【轉帖】設備性能測試 : 內存帶寬的測試

設備性能測試 : 內存帶寬的測試

版權聲明:本文爲博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處連接和本聲明。
本文連接: https://blog.csdn.net/pcokk/article/details/90733871

zero, 說明:

       爲了對系統的性能進行優化,於是須要分析系統的性能瓶頸在哪裏,須要對系統的一些設備的性能進行測試。這一篇文章用於記錄內存帶寬的測試,若是文章中有不足的地方,請不吝賜教。php

一,背景知識:

       下面提供一些關於內存結構的連接,如若侵權,請聯繫我進行刪除.
              CMU MainMemory
              圖解RAM結構與原理,系統記憶體的Channel、Chip與Bankhtml

二,實驗環境

       所使用的CPU的信息以下 : 一臺機器有24個物理核
CPU info
       所使用的內存條的信息以下 : 一臺機器有8個相同的內存條
Memory Info數組

三,初步測試

       最初打算尋找一些現有的工具對直接進行測試,測試的結果以下:
       1,使用dd命令進行測試,命令以下 :markdown

     dd if=/dev/zero of=/dev/shm/A bs=2M count=1024

 

       測試的結果以下 :
        dd comander's result
       顯然,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個線程,測試結果以下 :函數

       sysbench single thread
       從上圖能夠看出,單線程的狀況下,bindwidth爲 5.94GB/s。工具

       sysbench multi-thread
       從上圖能夠看出,多線程的狀況下,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;
 }

 

       這個函數的結果測試以下:
       simple for
       顯然,這結果不能使人滿意。

       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;
    }
 }

 

       這是函數的測試結果以下:
       k-fold
       結果與直接使用for循環的差異不大,緣由多是因爲編譯器進行優化,但具體還須要研究一下彙編,可是因爲沒有系統地學過彙編 : -( ,因此還須要進一步探究。。。。

       3), 使用操做系統提供的memset()函數 :

 static INLINE void function_memset(char *src, char value, unsigned int length){
    memset(src, value, length);
 }

 

       測試結果以下:
       memset
       能夠看出,內存的帶寬接近 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);
  }

 

       測試結果以下:
       SIMD
       能夠看出,其性能與直接使用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);
 }

 

       測試的結果以下 :
       Non-temporal Instruction
       能夠看出,可以達到了 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

相關文章
相關標籤/搜索