Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何最大化DDR3内存数据传输速率?_C#_.net_Memory_Memory Management_Ram - Fatal编程技术网

C# 如何最大化DDR3内存数据传输速率?

C# 如何最大化DDR3内存数据传输速率?,c#,.net,memory,memory-management,ram,C#,.net,Memory,Memory Management,Ram,我试图通过测试来测量DDR3内存数据传输速率。根据CPU规格,最大理论带宽为51.2 GB/s。这应该是四个通道的组合带宽,即12.8 GB/通道。然而,这是一个理论上的限制,我很好奇如何在这篇文章中进一步增加实际限制。在下面描述的测试场景中,我实现了~14 GB/s的数据传输速率,我认为,在杀死CPU L1、L2和L3缓存的大部分throughput提升时,这可能是一个近似值 2014年3月20日更新:这种杀死L1-L3缓存的假设是错误的。内存控制器的硬件预取将分析数据访问模式,因为它是顺序的

我试图通过测试来测量DDR3内存数据传输速率。根据CPU规格,最大理论带宽为51.2 GB/s。这应该是四个通道的组合带宽,即12.8 GB/通道。然而,这是一个理论上的限制,我很好奇如何在这篇文章中进一步增加实际限制。在下面描述的测试场景中,我实现了~14 GB/s的数据传输速率,我认为,在杀死CPU L1、L2和L3缓存的大部分throughput提升时,这可能是一个近似值

2014年3月20日更新:这种杀死L1-L3缓存的假设是错误的。内存控制器的硬件预取将分析数据访问模式,因为它是顺序的,所以它将很容易将数据预取到CPU缓存中

下面是一些具体的问题,但主要是我感兴趣的是a)对导致这个结果的假设的验证,以及b)是否有更好的方法在.NET中测量内存带宽。

我已经在C#on.NET中构建了一个测试作为起点。尽管从内存分配的角度来看.NET并不理想,但我认为它对于这个测试是可行的(如果您不同意,请告诉我原因)。测试是分配一个int64数组并用整数填充它。此阵列应在内存中对齐数据。然后,我简单地使用机器上的内核数量来循环这个数组,从数组中读取int64值,并将其设置为测试类中的本地公共字段。由于结果字段是公共的,我应该避免编译器优化循环中的内容。此外,这可能是一个很弱的假设,我认为结果保留在寄存器中,并且在再次重写之前不会写入内存。在每次读取数组中的元素之间,我在数组中使用10、100和1000的可变步长偏移量,以便无法在同一缓存块(64字节)中获取多个引用

从数组中读取Int64意味着查找读取8字节,然后再读取另一个8字节的实际值。由于数据是从64字节缓存线中的内存中获取的,因此在循环中每次读取的数据都应该对应于从RAM中读取的64字节,因为读取的数据不在任何CPU缓存中

以下是初始化数据数组的方法:

_longArray = new long[Config.NbrOfCores][];
for (int threadId = 0; threadId < Config.NbrOfCores; threadId++)
{
    _longArray[threadId] = new long[Config.NmbrOfRequests];
    for (int i = 0; i < Config.NmbrOfRequests; i++)
        _longArray[threadId][i] = i;
}
忽略提供实际的输出渲染代码,我得到以下结果:

Step   10: Throughput:   570,3 MReq/s and         34 GB/s (64B),   Timetaken/request:      1,8 ns/req, Total TimeTaken: 12624 msec, Total Requests:   7 200 000 000
Step  100: Throughput:   462,0 MReq/s and       27,5 GB/s (64B),   Timetaken/request:      2,2 ns/req, Total TimeTaken: 15586 msec, Total Requests:   7 200 000 000
Step 1000: Throughput:   236,6 MReq/s and       14,1 GB/s (64B),   Timetaken/request:      4,2 ns/req, Total TimeTaken: 30430 msec, Total Requests:   7 200 000 000
使用12个线程而不是6个线程(因为CPU是超线程的),我获得了几乎相同的吞吐量(我认为与预期的一样):32.9/30.2/15.5 GB/s

可以看出,吞吐量随着步长的增加而下降,我认为这是正常的。我认为部分原因是12MB的L3缓存导致更多的缓存未命中,部分原因可能是内存控制器预取机制在读取相距如此之远时无法正常工作。我进一步认为,step 1000的结果与实际内存速度最接近,因为它会杀死大多数CPU缓存,“希望”杀死预取机制。此外,我假设此循环中的大部分开销是内存获取操作,而不是其他操作

此测试的硬件为: 英特尔酷睿I7-3930(规格:、和),总共使用32 GB的DDR3-1600内存

开放性问题

  • 我的上述假设正确吗?

  • 有没有办法提高内存带宽的使用率?例如,用C/C++代替,在堆上分配更多的内存,使所有四个内存通道都能使用

  • 是否有更好的方法来测量内存数据传输?

  • 非常感谢您在这方面的意见。我知道这是一个复杂的引擎盖下的区域


    此处的所有代码可从下载。请随时通过发送电子邮件给gmail.com与我联系。

    C/C++将提供更准确的内存性能指标,因为.NET有时会在内存处理方面做一些奇怪的事情,并且不会提供准确的图片,因为它不使用编译器内部函数或SIMD指令

    不能保证CLR会为您提供任何真正能够对RAM进行基准测试的功能。我敢肯定,可能已经有软件编写了这样做。啊,是的,PassMark做了些什么:

    这可能是你最好的选择,因为制作基准测试软件几乎是他们的全部工作。 另外,很好的处理器顺便说一句,我的一台机器上有相同的处理器;)

    更新(2/20/2014): 我记得在XNA框架中看到一些代码,它们在C#中进行了一些繁重的优化,这可能会给您带来您想要的东西。您是否尝试过使用“不安全”代码和指针

    在最大内存带宽为51.2 GB/s的i7 3820上,我的bus8thread64.exe基准的报告RAM结果(128 MB)从15.6(1个线程)、28.1(2个线程)到38.7(8个线程)。代码是:

       void inc1word(IDEF data1[], IDEF ands[], int n)
        {
           int i, j;
    
           for(j=0; j<passes1; j++)
           {
               for (i=0; i<wordsToTest; i=i+64)
               {
                   ands[n] = ands[n] & data1[i   ] & data1[i+1 ] & data1[i+2 ] & data1[i+3 ]
                                     & data1[i+4 ] & data1[i+5 ] & data1[i+6 ] & data1[i+7 ]
                                     & data1[i+8 ] & data1[i+9 ] & data1[i+10] & data1[i+11]
                                     & data1[i+12] & data1[i+13] & data1[i+14] & data1[i+15]
                                     & data1[i+16] & data1[i+17] & data1[i+18] & data1[i+19]
                                     & data1[i+20] & data1[i+21] & data1[i+22] & data1[i+23]
                                     & data1[i+24] & data1[i+25] & data1[i+26] & data1[i+27]
                                     & data1[i+28] & data1[i+29] & data1[i+30] & data1[i+31]
                                     & data1[i+32] & data1[i+33] & data1[i+34] & data1[i+35]
                                     & data1[i+36] & data1[i+37] & data1[i+38] & data1[i+39]
                                     & data1[i+40] & data1[i+41] & data1[i+42] & data1[i+43]
                                     & data1[i+44] & data1[i+45] & data1[i+46] & data1[i+47]
                                     & data1[i+48] & data1[i+49] & data1[i+50] & data1[i+51]
                                     & data1[i+52] & data1[i+53] & data1[i+54] & data1[i+55]
                                     & data1[i+56] & data1[i+57] & data1[i+58] & data1[i+59]
                                     & data1[i+60] & data1[i+61] & data1[i+62] & data1[i+63];
               }
            }
        }
    
    void inc1word(IDEF data1[],IDEF and[],int n)
    {
    int i,j;
    
    对于(j=0;j而言,随着步长的增加,吞吐量的降低很可能是由于内存预取不再正常工作而导致的,如果您不以线性方式跨过内存

    您可以采取哪些措施来提高速度:

    • 测试速度将被占用CPU周期的循环本身人为限制。如罗伊所示,通过展开循环可以获得更高的速度
    • 您应该取消边界检查(使用“unchecked”)
    • 不要使用并行。对于,请使用线程。启动并将启动的每个线程固定在单独的内核上(使用此处的代码:)
    • 确保所有线程同时启动,这样就不会测量任何掉队的线程(可以通过在联锁的内存地址上旋转来执行此操作。将交换到新的va
      Step   10: Throughput:   570,3 MReq/s and         34 GB/s (64B),   Timetaken/request:      1,8 ns/req, Total TimeTaken: 12624 msec, Total Requests:   7 200 000 000
      Step  100: Throughput:   462,0 MReq/s and       27,5 GB/s (64B),   Timetaken/request:      2,2 ns/req, Total TimeTaken: 15586 msec, Total Requests:   7 200 000 000
      Step 1000: Throughput:   236,6 MReq/s and       14,1 GB/s (64B),   Timetaken/request:      4,2 ns/req, Total TimeTaken: 30430 msec, Total Requests:   7 200 000 000
      
         void inc1word(IDEF data1[], IDEF ands[], int n)
          {
             int i, j;
      
             for(j=0; j<passes1; j++)
             {
                 for (i=0; i<wordsToTest; i=i+64)
                 {
                     ands[n] = ands[n] & data1[i   ] & data1[i+1 ] & data1[i+2 ] & data1[i+3 ]
                                       & data1[i+4 ] & data1[i+5 ] & data1[i+6 ] & data1[i+7 ]
                                       & data1[i+8 ] & data1[i+9 ] & data1[i+10] & data1[i+11]
                                       & data1[i+12] & data1[i+13] & data1[i+14] & data1[i+15]
                                       & data1[i+16] & data1[i+17] & data1[i+18] & data1[i+19]
                                       & data1[i+20] & data1[i+21] & data1[i+22] & data1[i+23]
                                       & data1[i+24] & data1[i+25] & data1[i+26] & data1[i+27]
                                       & data1[i+28] & data1[i+29] & data1[i+30] & data1[i+31]
                                       & data1[i+32] & data1[i+33] & data1[i+34] & data1[i+35]
                                       & data1[i+36] & data1[i+37] & data1[i+38] & data1[i+39]
                                       & data1[i+40] & data1[i+41] & data1[i+42] & data1[i+43]
                                       & data1[i+44] & data1[i+45] & data1[i+46] & data1[i+47]
                                       & data1[i+48] & data1[i+49] & data1[i+50] & data1[i+51]
                                       & data1[i+52] & data1[i+53] & data1[i+54] & data1[i+55]
                                       & data1[i+56] & data1[i+57] & data1[i+58] & data1[i+59]
                                       & data1[i+60] & data1[i+61] & data1[i+62] & data1[i+63];
                 }
              }
          }