微吼云上線多路互動直播服務 加速多場景互動直播落地
1406
2025-04-02
特戰隊六月底成立,至今百日有余,恰逢1024程序員節,遂整理此文,獻禮致敬!希望能為廣大在鯤鵬處理器上開發軟件、性能調優的程序員們,提供一點幫助。從今天開始,將陸續推出性能調優專題文章。
1 應用程序調優
1.1? 調優簡介
1.2? 優化方法
1.1 調優簡介
應用程序部署到鯤鵬服務器上以后,需要結合芯片和服務器的特點優化代碼性能,使硬件能力得到充分發揮。本章列舉幾個典型場景,涉及鎖、編譯器配置、Cacheline、緩沖機制等優化。
1.2 優化方法
1.2.1 優化編譯選項,提升程序性能
原理
C/C++代碼在編譯時,gcc編譯器將源碼翻譯成CPU可識別的指令序列,寫入可執行程序的二進制文件中。CPU在執行指令時,通常采用流水線的方式并行執行指令,以提高性能,因此指令執行順序的編排將對流水線執行效率有很大影響。通常在指令流水線中要考慮:執行指令計算的硬件資源數量、不同指令的執行周期、指令間的數據依賴等等因素。我們可以通過通知編譯器,程序所運行的目標平臺(CPU)指令集、流水線,來獲取更好的指令序列編排。在gcc 9.1.0版本,支持了鯤鵬處理器所兼容的armv8指令集、tsv110流水線。
修改方式
l?? 在Euler系統中使用HCC編譯器,可以在CFLAGS和CPPFLAGS里面增加編譯選項:
-mtune=tsv110 -march=armv8-a
l?? 在其它操作系統中,可以升級GCC版本到9.10,并在CFLAGS和CPPFLAGS里面增加編譯選項:
-mtune=tsv110 -march=armv8-a
1.2.2 文件緩沖機制選擇
原理
內存訪問速度要高于磁盤,應用程序在讀寫磁盤時,通常會經過一些緩存,以減少對磁盤的直接訪問,如下圖所示:
clib buffer:clib buffer是用戶態的一種數據緩沖機制,在啟用clib buffer的情況下,數據從應用程序的buffer拷貝至clib buffer后,并不會立即將數據同步到內核,而是緩沖到一定規模或者主動觸發的情況下,才會同步到內核;當查詢數據時,會優先從clib buffer查詢數據。這個機制能減少用戶態和內核態的切換(用戶態切換內核態占用一定資源)。
PageCache:PageCache是內核態的一種文件緩存機制,用戶在讀寫文件時,先操作PageCache,內核根據調度機制或者被應用程序主動觸發時,會將數據同步到磁盤。PageCache機制能減少磁盤訪問。
修改方式
應用程序根據自己的業務特點選擇合適的文件讀寫方式:
l?? fread/fwrite函數使用了clib buffer緩存機制,而read/write并沒有使用,因此fread/fwrite比read/write多一層內存拷貝,即從應用程序buffer至clib buffer的拷貝,但是fread/fwrite比read/write有更少的系統調用。因此對于每次讀寫字節數較大的操作,內存拷貝比系統調用占用更多資源,可以使用read/write來減少內存拷貝;對于每次讀寫字節數較少的操作,系統調用比內存拷貝占用更多資源,建議使用fread/ fwrite來減少系統調用次數。
l?? O_DIRECT模式沒有使用PageCache,因此少了一層內存拷貝,但是因為沒有緩沖導致每次都是從磁盤里面讀取數據。
O_DIRECT主要適用場景為:應用程序有自己的緩沖機制;數據讀寫一次后,后面不再從磁盤讀這個數據。
1.2.3 執行結果緩存
原理
對于相同的輸入,應用軟件經過計算后,有相同的輸出,可以將運算結果保存,在下次有相同的輸入時,返回上次執行的結果。
修改方式
目前部分開源軟件已經實現這種機制,舉例如下:
1.???????? Nginx緩沖
基于局部性原理,Nginx使用proxy_cache_path等參數將請求過的內容在本地內存建立一個副本,這樣對于緩存中的文件不用去后端服務器去取。
2.???????? JIT編譯
JIT(Just-In-Time)編譯,將輸入文件轉為機器碼。為了提升效率,轉換后的機器碼被緩存在內存,這樣相同的輸入(如JAVA程序的字節碼)不用重新翻譯,直接返回緩存中的內容。如果發現JAVA虛擬機的C1 Compiler/C2 Compiler線程的CPU占用比較多,可能是JIT緩沖不夠,可以增加JAVA虛擬機ReservedCodeCacheSize參數。
1.2.4 減少內存拷貝
原理
基于數據流分析,發現并減少內存拷貝次數,能降低CPU使用率,并減少內存帶寬占用。
修改方式
減少內存拷貝要基于業務邏輯進行分析,這里例舉幾種減少內存拷貝的實現機制:
l?? 樣例一:使用sendfile代替send/sendto/write等函數將文件發送給對端
如下兩條語句將文件發送給對端,一般會有4次內存拷貝
ssize_t read (int fd, void *buf, size_t count);
ssize_t send (int s, const void * buf, size_t len, int flags);
??????????? read函數一般有2次內存拷貝: DMA將數據搬運到內核的PageCache;內核將數據搬運到應用態的buf。
??????????? send函數一般有2次內存拷貝: write函數將應用態的buf的拷貝到內核;DMA將數據搬運到網卡。
使用如下函數實現只需要兩次內存拷貝:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
內核通過 DMA將文件搬運到緩存(一次內存拷貝),然后把緩存的描述信息(位置和長度)傳遞給TCP/IP協議棧,內核在通過DMA將緩沖搬運到網卡(第二次內存拷貝)。
除了修改代碼,部分開源軟件已經支持這個特性,如Nginx可以通過sendfile on參數打開這個功能。
l?? 樣例二:進程間通信使用共享內存代替socket/pipe通信
內存共享方式可以讓多個進程操作同樣的內存區域,相比socket通信的的方式,內存拷貝少。應用程序可以使用shmget等函數實現進程間通信。
1.2.5 鎖優化
原理
自旋鎖和CAS指令都是基于原子操作指令實現,當應用程序在執行原子操作失敗后,并不會釋放CPU資源,而是一直循環運行直到原子操作執行成功為止,導致CPU資源浪費。如下圖代碼的黃色部分是一個循環等待過程:
修改方式
可以通過perf top分析占用CPU資源靠前的函數,如果鎖的申請和釋放在5%以上,可以考慮優化鎖的實現,修改思路如下:
1.???????? 大鎖變小鎖:并發任務高的場景下,如果系統中存在唯一的全局變量,那么每個CPU core都會申請這個全局變量對應的鎖,導致這個鎖的爭搶嚴重。可以基于業務邏輯,為每個CPU core或者線程分配對應的資源。
2.???????? 使用ldaxr+stlxr兩條指令實現原子操作時,可以同時保證內存一致性,而ldxr+stxr指令并不能保存內存一致性,從而需要內存屏障指令(dmb ish)配合來實現內存一致性。從測試情況看,ldaxr+stlxr指令比ldxr+stxr+dmb ish指令的性能高。
3.???????? 減少線程并發數:參考2.3.5 調整線程并發數章節。
4.???????? 對鎖變量使用Cacheline對齊:對于高頻訪問的鎖變量,實際是對鎖變量進行高頻的讀寫操作,容易發生偽共享問題。具體優化可以參考5.2.7 Cacheline 優化章節。
5.???????? 優化代碼中原子操作的實現。下圖代碼為某軟件的代碼實現:
從函數調用邏輯上看,在while循環會重復執行原子讀、變量加以及原子寫入操作,代碼語句多。優化思路:使用atomic_add_return指令替換這個代碼流程,簡化指令,提高性能。替換后的代碼如下圖:
1.2.6 使用jemalloc優化內存分配
原理
jemalloc是一款內存分配器,與其它內存分配器(glibc)相比,其最大優勢在于多線程場景下內存分配性能高以及內存碎片減少。充分發揮鯤鵬芯片多核多并發優勢,推薦業務應用代碼使用jemalloc進行內存分配。
在內存分配過程中,鎖會造成線程等待,對性能影響巨大。jemalloc采用如下措施避免線程競爭鎖的發生:使用線程變量,每個線程有自己的內存管理器,分配在這個線程內完成,就不需要和其它線程競爭鎖。
修改方式
步驟 1????? 下載jemalloc,參考INSTALL.md編譯安裝。
源碼-https://github.com/jemalloc/jemalloc
步驟 2????? 修改應用軟件的鏈接庫的方式,在編譯選項中添加如下編譯選項:
-I`jemalloc-config --includedir`-L`jemalloc-config --libdir` -Wl,-rpath,`jemalloc-config --libdir` -ljemalloc `jemalloc-config --libs`
具體參考 https://github.com/jemalloc/jemalloc/wiki/Getting-Started
步驟 3????? 部分開源軟件可以修改配置參數來指定內存分配庫,如MySql可以配置my.cnf文件:malloc-lib=/usr/local/lib/libjemalloc.so
----結束
1.2.7 Cacheline 優化
原理
CPU標識Cache中的數據是否為有效數據不是以內存位寬為單位,而是以Cacheline為單位。這個機制可能會導致偽共享(false sharing)現象,從而使得CPU的Cache命中率變低。出現偽共享的常見原因是高頻訪問的數據未按照Cacheline大小對齊:
Cache空間大小劃分成不同的Cacheline,示意圖如下,readHighFreq雖然沒有被改寫,且在Cache中,在發生偽共享時,也是從內存中讀:
例如以下代碼定義兩個變量,會在同一個Cacheline中,Cache會同時讀入:
int readHighFreq, writeHighFreq
其中readHighFreq是讀頻率高的變量,writeHighFreq為寫頻率高的變量。writeHighFreq在一個CPU core里面被改寫后,這個cache 中對應的Cacheline長度的數據被標識為無效,也就是readHighFreq被CPU core標識為無效數據,雖然readHighFreq并沒有被修改,但是CPU在訪問readHighFreq時,依然會從內存重新導入,出現偽共享導致性能降低。
鯤鵬920和x86的Cacheline大小不一致,可能會出現在X86上優化好的程序在鯤鵬 920上運行時的性能偏低的情況,需要重新修改業務代碼數據內存對齊大小。X86 L3 cache的Cacheline大小為64字節,鯤鵬920的Cacheline為128字節。
修改方式
1.???????? 修改業務代碼,使得讀寫頻繁的數據以Cacheline大小對齊,修改方法可參考:
a.???????? 使用動態申請內存的對齊方法:
int posix_memalign(void **memptr, size_t alignment, size_t size)
調用posix_memalign函數成功時會返回size字節的動態內存,并且這塊內存的起始地址是alignment的倍數。
b.???????? 局部變量可以采用填充的方式:
int writeHighFreq;
char pad[CACHE_LINE_SIZE - sizeof(int)];
代碼中CACHE_LINE_SIZE是服務器Cacheline的大小,pad變量沒有用處,用于填充writeHighFreq變量余下的空間,兩者之和是CacheLine大小。
2.???????? 部分開源軟件代碼中有Cacheline的宏定義,修改宏的值即可。如在impala使用CACHE_LINE_SIZE宏來表示目標平臺的Cacheline大小。
鯤鵬
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。