性能優(yōu)化-放開那片內(nèi)存,讓我來!
本文轉(zhuǎn)載自微信公眾號「編程珠璣」,作者守望先生。轉(zhuǎn)載本文請聯(lián)系編程珠璣公眾號。
性能優(yōu)化是一個(gè)常有的事情,通常來說
- 不要過早優(yōu)化-當(dāng)你沒有性能問題時(shí),不需要過早考慮優(yōu)化,當(dāng)然對于一些代價(jià)很小,收益卻很大的手段可以考慮做進(jìn)來,例如最常見的就是根據(jù)業(yè)務(wù)需求選擇合適的數(shù)據(jù)結(jié)構(gòu)。
- 不要過度優(yōu)化。優(yōu)化都是有目標(biāo)的,比如你需要達(dá)到多少TPS,那么你按照這個(gè)目標(biāo)去優(yōu)化即可,有些優(yōu)化雖然能否提升性能,但可能對代碼的可維護(hù)性造成破壞。
本人對此沒有過多涉獵,僅分享工作中接觸到的一些內(nèi)存。
內(nèi)存性能問題
有很多方面會造成性能問題,例如:
- 業(yè)務(wù)流程設(shè)計(jì)不合理,導(dǎo)致很多沒有必要的計(jì)算
- 數(shù)據(jù)結(jié)構(gòu)選擇不合適
- 緩存使用不當(dāng)
示例
假設(shè)你已經(jīng)通過《perf:一個(gè)命令發(fā)現(xiàn)性能問題》中的方法或者使用profiler分析,已經(jīng)發(fā)現(xiàn)內(nèi)存分配是性能瓶頸:
- // 來源:公眾號【編程珠璣】
- // 作者:守望先生
- // 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;
- }
代碼非常簡單,僅僅是不斷分配內(nèi)存而已。
編譯并嘗試分配十億次:
- $ g++ -g -o malloc malloc.cc -lpthread
- $ time ./malloc
- real 0m8.677s
- user 0m29.409s
- sys 0m0.029s
分配十億次內(nèi)存,使用時(shí)間大概17s左右。另外一個(gè)終端使用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
從結(jié)果可以看到,大部分CPU耗費(fèi)在了內(nèi)存的申請和釋放。
怎么辦呢?第一要考慮的做法不是如何提升它,而是它能否避免?比如內(nèi)存復(fù)用?而非反復(fù)申請?
比如使用內(nèi)存池?但是要自己寫一個(gè)穩(wěn)定的內(nèi)存池又需要耗費(fèi)很大的精力了。怎么辦呢?
性能更好的庫
實(shí)際上這就引出了性能優(yōu)化的一種常見方法-使用性能更好的庫。那么在內(nèi)存分配方面,有更好的庫嗎?自己又不能寫出一個(gè)比libc還厲害的庫,就只能用用開源的庫,才能維持得了寫代碼的生活。
目前常見的性能比較好的內(nèi)存分配庫有
- tcmalloc-谷歌開發(fā)的內(nèi)存分配庫
- jemalloc
在自己編譯使用redis的時(shí)候,其實(shí)你能看到它們的身影:
- # 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,小塊的內(nèi)存分配都可以從cache中分配。多線程分配內(nèi)存的情況下,可以減少鎖競爭。
獲取
你可以通過源碼編譯獲取,github地址:https://github.com/google/tcmalloc.git
不過它需要使用bazel進(jìn)行構(gòu)建編譯,有興趣的可以自行嘗試。
也可以直接安裝:
- $ 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
LD_PRELOAD
這種方式在自己測試的時(shí)候非常方便,只需要:
- $ export LD_PRELOAD=/path/to/tcmalloc.so
導(dǎo)入環(huán)境變量,指定庫路徑即可。注意這里的/path/to更換成你的tcmalloc實(shí)際的路徑。運(yùn)行的時(shí)候,tcmalloc庫就會被首先被使用了。
直接鏈接
這種方法就和普通庫的使用沒有什么區(qū)別了,鏈接使用就完事了。相關(guān)文章《靜態(tài)庫的制作與使用》
效果
我們使用新的庫,再進(jìn)行10億次的內(nèi)存分配試試:
- $ time ./malloc
- real 0m7.152s
- user 0m27.997s
- sys 0m0.032s
可以看到要使用的時(shí)間少了些。當(dāng)然,這里的對比嚴(yán)格來說不是很嚴(yán)謹(jǐn),甚至可以說起不到對比的作用。首先這里內(nèi)存分配大小比較單一,并且僅有內(nèi)存分配,而沒有其他處理,真正是否有效果,還是要根據(jù)實(shí)際業(yè)務(wù)程序的情況來判斷。當(dāng)然,整體來說,tcmalloc的效果要比libc的malloc分配內(nèi)存要高效。
總結(jié)
當(dāng)你的程序中存在大量的內(nèi)存分配(例如C++頻繁使用string),那么可以考慮使用性能更好的內(nèi)存分配庫了。關(guān)于tcmalloc,jemalloc等內(nèi)存分配庫的對比有很多,這里有興趣的可自行了解。
作者:守望,linux應(yīng)用開發(fā)者,目前在公眾號【編程珠璣】?分享Linux/C/C++/數(shù)據(jù)結(jié)構(gòu)與算法/工具等原創(chuàng)技術(shù)文章和學(xué)習(xí)資源。