爲了對系統的性能進行優化,於是須要分析系統的性能瓶頸在哪裏,須要對系統的一些設備的性能進行測試。這一篇文章用於記錄內存帶寬的測試,若是文章中有不足的地方,請不吝賜教。php
下面提供一些關於內存結構的連接,如若侵權,請聯繫我進行刪除.
CMU MainMemory
圖解RAM結構與原理,系統記憶體的Channel、Chip與Bankhtml
所使用的CPU的信息以下 : 一臺機器有24個物理核
所使用的內存條的信息以下 : 一臺機器有8個相同的內存條數組
最初打算尋找一些現有的工具對直接進行測試,測試的結果以下:
1,使用dd命令進行測試,命令以下 :markdown
dd if=/dev/zero of=/dev/shm/A bs=2M count=1024
測試的結果以下 :
顯然,2.7GB/s的內存帶寬這個結果,是不能使人滿意的。多線程
2,使用mbw命令進行測試,命令以下:架構
mbw 16 -b 4096
測試的結果以下 : (進行了屢次測試,其中選取測試結果表現最好)
因爲mbw使用了三種不一樣的方式進行了測試 :
(1), 使用memcpy將一個數組複製到另外一個數組 :
其avg bandwidth爲5.2GB/s,可是因爲須要從一個數組複製到另外一個,因此應該包括內存讀和內存寫,假設讀寫速度同樣的話,其avg bandwidth應該爲 5.2 * 2 = 10.4GB/s
(2), 使用for循環將一個數組複製到另外一個數組 :
同理,能夠看出其avg bandwidth爲 12.2 GB/s
(3), 使用mempcpy將一個塊複製到一個數組 :
因爲只是重複地複製一個塊,因此能夠看作只有內存寫操做,故其avg bandwidth爲 12.2GB/sapp
3, 使用sysbench進行測試,測試命令以下ide
sysbench --test=memory --memory-block-size=4K --memory-totol-size=2G --num-threads=1 run sysbench --test=memory --memory-block-size=4K --memory-totol-size=2G --num-threads=16 run
其中第一個命令使用了1個線程,第二個命令使用了16個線程,測試結果以下 :函數
從上圖能夠看出,單線程的狀況下,bindwidth爲 5.94GB/s。工具
從上圖能夠看出,多線程的狀況下,bindwidth爲 7.8 GB/s。
因爲目前還沒有了解sysbench是將一個塊重複複製到一個數組中,仍是將一個數組複製到另外一個數組中。因此假設是將一個塊重複複製,那麼其bandwidth在單線程和多線程的狀況下分別爲5.94GB/s , 7.83GB/s
後來和同窗的討論下,能夠根據內存條的參數計算bandwidth的峯值,計算以下 :
由於內存條的頻率爲2400 MHz, 數據寬度爲64bit,假設一個時鐘週期能進行一次操做的話,那麼最高的帶寬爲 :x=24001000×648=19.2GB/sx = \dfrac{2400}{1000} \times \dfrac{64}{8} = 19.2 GB/sx=10002400×864=19.2GB/s
因此查找到了一些資料[1] [2],打算根據這些資料,本身寫一個程序來測試內存的帶寬。
本來打算使用將一個數組複製到另外一個數組的方式,可是考慮到這樣須要讀一遍內存,再寫一遍內存,感受效率比較低。因此採用將一個字符直接寫到一個數組中的方法,這樣能夠認爲只有單獨的寫操做,由於一個字符可能會存放在寄存器或cache中,就無需重複地讀取內存。
1,基本的測試函數體以下:
#define G (1024*1024*1024LL) #define NS_PER_S 1000000000.0 #define INLINE inline __attribute__((always_inline)) char src[2*G] __attribute__((aligned(32))); char dst[2*G] __attribute__((aligned(32))); int main(int argc, char* argv[]){ struct timespec start, end; unsigned int length = (unsigned int)2*G; memset(src, 1, length); memset(dst, 0, length); //這兩個memset的做用是訪問數組後,保證能加載全部的內存頁,防止因爲缺頁中斷影響測試的結果 clock_gettime(CLOCK_MONOTONIC, &start); /* * 這裏是不一樣實現的memset函數 */ clock_gettime(CLOCK_MONOTONIC, &end); double timeuse = NS_PER_S * (end.tv_sec - start.tv_sec) + end.tv_nsec - start.tv_nsec; double s = timeuse / NS_PER_S; printf("timeval = %lf, io speed is %lf\n", s, length/G/s); return 0; }
2, 使用memset()測試
編譯使用的命令 :
gcc ./memory_io_v4.c -o memory_io -O3 -mavx -mavx2 -msse3 -lrt
1),使用簡單的for循環語句:
static INLINE void function_memset_for(char *src, char value, unsigned int length){ for(unsigned int i = 0; i < length; i++) src[i] = value; }
這個函數的結果測試以下:
顯然,這結果不能使人滿意。
2), 使用CSAPP中第5章提到的k路展開,k路並行(這裏使用的k = 4) :
static INLINE void function_memset_k_fold(char *src, char value, unsigned int length){ for(unsigned int i = 0; i < length; i+=4){ src[i] = value; src[i+1] = value; src[i+2] = value; src[i+3] = value; } }
這是函數的測試結果以下:
結果與直接使用for循環的差異不大,緣由多是因爲編譯器進行優化,但具體還須要研究一下彙編,可是因爲沒有系統地學過彙編 : -( ,因此還須要進一步探究。。。。
3), 使用操做系統提供的memset()函數 :
static INLINE void function_memset(char *src, char value, unsigned int length){ memset(src, value, length); }
測試結果以下:
能夠看出,內存的帶寬接近 8 GB/s, 比上面的函數高出許多,但仍是不能達不到理想狀態。
4), 使用SIMD指令 :
static INLINE void function_memset_SIMD_32B(char *src, char value, unsigned int length){ __m256i *vsrc = (__m256i *)src; __256i ymm0 = _mm256_set_epi8(value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value); unsigned int len = length / 32; for(unsigned int i = 0; i < len; i++) _mm256_storeu_si256(&vsrc[i], ymm0); }
測試結果以下:
能夠看出,其性能與直接使用memset()的效果同樣。目前猜想其緣由是在不一樣的架構中,這些基本函數都會使用匯編語言進行實現,從而確保更高的性能,因此二者可以達到一樣的性能。(這個須要在學完彙編後進一步驗證。)。
並且在實驗過程當中,還分別使用了一次讀取64bit, 128bit的SIMD指令,其結果和上面所使用的一次讀取256bit的SIMD指令的結果相差不大,這裏的緣由也須要探究。
5), 根據參考資料[2], 可使用Non-temporal Instruction,避免一些cache的問題
static INLINE void function_memset_SIMD_s32B(char *src, char value, unsigned int length){ __m256i *vsrc = (__m256i *)src; __m256i ymm0 = _mm256_set_epi8(value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value, value); unsigned int len = length / 32; for(unsigned int i = 0; i < len; i++) _mm256_stream_si256(&vsrc[i], ymm0); }
測試的結果以下 :
能夠看出,可以達到了 15.5GB/s 的帶寬。
關於爲何相比以前的能達到這麼高的bandwidth,請見資料[2]中的解釋,具體以下,因爲每次寫32B,而且每一個cache line的大小爲32B,也就是若是不使用Non-temporal Instruction, 每次寫的時候,先寫到cache line中,最後會將cache line寫到內存,因爲是遍歷訪問數組,即每次寫32B,須要先將數組從內存讀到cache line,再寫cache line,最後cache line寫回內存,至關於每次須要兩次內存訪問;而使用了Non-temporal Instruction,能夠直接寫到內存中,這樣只須要一次內存訪問。即便這樣,可是仍是不盡人意。
6), 使用rep指令,這裏使用與參考資料[2]同樣的程序,可是效果卻不佳,結果以下 :
涉及彙編指令的東西目前都尚不能解決,須要做進一步探究。
7), 使用Multi-core
關於參考資料中使用Multi-core的實驗還未作,由於可能與NUMA架構相關。因此暫且放一放。
這篇文章記錄了測試內存帶寬的過程,包括使用的一些Ubuntu系統的測試工具dd, mbw, sysbench,以及本身根據資料編寫的代碼,能夠看出,最高能到達到一個內存條帶寬的80%。
主要存在下面的三個問題還未解決 :
1, 關於一些涉及到彙編指令的測試結果還未能解釋。
2, 根據內存條的標籤的參數,以及機器的架構(NUMA, dual channel),能夠計算出每一個內存條的峯值爲19.6GB/s, 而且機器是四通道,因此理論上一個Socket能達到的內存峯值爲78.4GB/s.有沒有什麼方法可以利用機器提供的多通道來達到這個內存bandwidth呢?
3,有沒有辦法到達一個內存條更高的bandwidth,而不僅是80%?
關於以上兩個問題,若是有大佬可以指點一二,或者提供一些資料,不勝感激。
[1],SIMD Instructions Official
[2],Achieving maximum memory bandwidth
[3],Testing Memory I/O Bandwidth