性能優化是一個常有的事情,通常來說
本人對此沒有過多涉獵,僅分享工作中接觸到的一些內存。
內存性能問題有很多方面會造成性能問題,例如:
業務流程設計不合理,導致很多沒有必要的計算
數據結構選擇不合適
緩存使用不當
示例假設你已經者使用profiler分析,已經發現內存分配是性能瓶頸:
// 來源:公眾號【編程珠璣】
// 作者:守望先生
// malloc.cc
#include <thread>
#include <vector>
#include <stdlib.h>
#include <string.h>
void GetMemory(){
for(int i = 0;i < 100000000; i++){
void *p = malloc(1024);
if(NULL != p){
free(p);
p = NULL;
}
}
}
int main(){
std::vector<std::thread> th;
int nr_threads = 10;
for (int i = 0; i < nr_threads; ++i) {
th.push_back(std::thread(GetMemory));
}
for(auto &t : th){
t.join();
}
return 0;
}
代碼非常簡單,僅僅是不斷分配內存而已。
編譯並嘗試分配十億次:
$ g++ -g -o malloc malloc.cc -lpthread
$ time ./malloc
real 0m8.677s
user 0m29.409s
sys 0m0.029s
分配十億次內存,使用時間大概17s左右。另外一個終端使用perf查看情況:
$ perf top -p `pidof malloc`
52.92% libc-2.27.so [.] cfree@GLIBC_2.2.5
31.94% libc-2.27.so [.] malloc
8.82% malloc [.] GetMemory
3.45% malloc [.] free@plt
2.51% malloc [.] malloc@plt
0.03% [kernel] [k] prepare_exit_to_usermode
0.01% [kernel] [k] psi_task_change
0.01% [kernel] [k] native_irq_return_iret
0.01% [kernel] [k] __update_load_avg_cfs_rq
0.01% [kernel] [k] __update_load_avg_se
0.01% [kernel] [k] update_curr
0.01% [kernel] [k] native_write_msr
0.01% [kernel] [k] __schedule
0.01% [kernel] [k] native_read_msr
0.01% [kernel] [k] read_tsc
0.01% [kernel] [k] interrupt_entry
0.01% [kernel] [k] update_load_avg
0.01% [kernel] [k] swapgs_restore_regs_and_return_to_usermode
0.01% [kernel] [k] reweight_entity
0.01% [kernel] [k] switch_fpu_return
0.01% [kernel] [k] perf_event_task_tick
從結果可以看到,大部分CPU耗費在了內存的申請和釋放。
怎麼辦呢?第一要考慮的做法不是如何提升它,而是它能否避免?比如內存復用?而非反覆申請?
比如使用內存池?但是要自己寫一個穩定的內存池又需要耗費很大的精力了。怎麼辦呢?
實際上這就引出了性能優化的一種常見方法-使用性能更好的庫。那麼在內存分配方面,有更好的庫嗎?自己又不能寫出一個比libc還厲害的庫,就只能用用開源的庫,才能維持得了寫代碼的生活。
目前常見的性能比較好的內存分配庫有
tcmalloc-谷歌開發的內存分配庫
jemalloc
在自己編譯使用redis的時候,其實你能看到它們的身影:
# Backwards compatibility for selecting an allocator
ifeq ($(USE_TCMALLOC),yes)
MALLOC=tcmalloc
endif
ifeq ($(USE_TCMALLOC_MINIMAL),yes)
MALLOC=tcmalloc_minimal
endif
ifeq ($(USE_JEMALLOC),yes)
MALLOC=jemalloc
endif
ifeq ($(USE_JEMALLOC),no)
MALLOC=libc
endif
這裡以tcmalloc為例,看一下如何使用該庫替換libc中的malloc。tcmalloc使用了thread cache,小塊的內存分配都可以從cache中分配。多線程分配內存的情況下,可以減少鎖競爭。
獲取你可以通過源碼編譯獲取,github地址:https://github.com/google/tcmalloc.git
不過它需要使用bazel進行構建編譯,有興趣的可以自行嘗試。
也可以直接安裝:
$ apt-get install -y libtcmalloc-minimal4
安裝位置查看:
$ ldconfig -p | grep tcmalloc
libtcmalloc_minimal_debug.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal_debug.so.4
libtcmalloc_minimal.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4
libtcmalloc_debug.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_debug.so.4
libtcmalloc_and_profiler.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc_and_profiler.so.4
libtcmalloc.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libtcmalloc.so.4
這種方式在自己測試的時候非常方便,只需要:
$ export LD_PRELOAD=/path/to/tcmalloc.so
導入環境變量,指定庫路徑即可。注意這裡的/path/to更換成你的tcmalloc實際的路徑。運行的時候,tcmalloc庫就會被首先被使用了。
直接連結這種方法就和普通庫的使用沒有什麼區別了,連結使用就完事了。
效果我們使用新的庫,再進行10億次的內存分配試試:
$ time ./malloc
real 0m7.152s
user 0m27.997s
sys 0m0.032s
可以看到要使用的時間少了些。當然,這裡的對比嚴格來說不是很嚴謹,甚至可以說起不到對比的作用。首先這裡內存分配大小比較單一,並且僅有內存分配,而沒有其他處理,真正是否有效果,還是要根據實際業務程序的情況來判斷。當然,整體來說,tcmalloc的效果要比libc的malloc分配內存要高效。
總結當你的程序中存在大量的內存分配(例如C++頻繁使用string),那麼可以考慮使用性能更好的內存分配庫了。關於tcmalloc,jemalloc等內存分配庫的對比有很多,這裡有興趣的可自行了解。
添加良許個人微信即送3套程式設計師必讀資料
→ 精選技術資料共享
→ 高手如雲交流社群
本公眾號全部博文已整理成一個目錄,請在公眾號裡回復「m」獲取!推薦閱讀:
是時候跟Docker說再見了
Linux系統inodes資源耗盡問題
如何從一張外國軍車照片,判斷它要去哪裡?
5T技術資源大放送!包括但不限於:C/C++,Linux,Python,Java,PHP,人工智慧,單片機,樹莓派,等等。在公眾號內回復「1024」,即可免費獲取!!