【转帖】设备性能测试 : 内存带宽的测试

设备性能测试 : 内存带宽的测试

版权声明:本文为博主原创文章,遵循 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

相关文章
相关标签/搜索