gpt4 book ai didi

c# - 如何最大化DDR3内存数据传输率?

转载 作者:IT王子 更新时间:2023-10-28 23:31:22 26 4
gpt4 key购买 nike

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

2014 年 3 月 20 日更新:这种杀死 L1-L3 缓存的假设是错误的。内存 Controller 的硬件预取将分析数据访问模式,并且由于它是顺序的,因此将数据预取到 CPU 缓存中是一项简单的任务。

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

作为初学者,我在 .NET 上用 C# 构建了一个测试。虽然 .NET 从内存分配的角度来看并不理想,但我认为它对于这个测试是可行的(如果你不同意,请告诉我为什么)。测试是分配一个 int64 数组并用整数填充它。这个数组应该在内存中对齐数据。然后我简单地使用与机器上的内核数量一样多的线程来循环这个数组,并从数组中读取 int64 值并将其设置为测试类中的本地公共(public)字段。由于结果字段是公开的,我应该避免编译器优化掉循环中的东西。此外,这可能是一个弱假设,我认为结果保留在寄存器中并且不会写入内存,直到它再次被覆盖。在每次读取数组中的元素之间,我在数组中使用 10、100 和 1000 的可变步长偏移量,以便无法在同一缓存块(64 字节)中获取许多引用。

从数组中读取 Int64 应该意味着查找读取 8 个字节,然后读取另一个 8 个字节的实际值。由于数据是从 64 字节高速缓存行中的内存中获取的,如果读取的数据不位于任何 CPU 高速缓存中,则数组中的每次读取都应对应于循环中每次从 RAM 中读取的 64 字节。

这是我初始化数据数组的方法:

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

这是实际测试:
GC.Collect();
timer.Start();
Parallel.For(0, Config.NbrOfCores, threadId =>
{
var intArrayPerThread = _longArray[threadId];
for (int redo = 0; redo < Config.NbrOfRedos; redo++)
for (long i = 0; i < Config.NmbrOfRequests; i += Config.Step)
_result = intArrayPerThread[i];
});
timer.Stop();

由于数据摘要对结果非常重要,我也提供了此信息(如果您相信我,可以跳过...)
var timetakenInSec = timer.ElapsedMilliseconds / (double)1000;
long totalNbrOfRequest = Config.NmbrOfRequests / Config.Step * Config.NbrOfCores*Config.NbrOfRedos;
var throughput_ReqPerSec = totalNbrOfRequest / timetakenInSec;
var throughput_BytesPerSec = throughput_ReqPerSec * byteSizePerRequest;
var timeTakenPerRequestInNanos = Math.Round(1e6 * timer.ElapsedMilliseconds / totalNbrOfRequest, 1);
var resultMReqPerSec = Math.Round(throughput_ReqPerSec/1e6, 1);
var resultGBPerSec = Math.Round(throughput_BytesPerSec/1073741824, 1);
var resultTimeTakenInSec = Math.Round(timetakenInSec, 1);

忽略给你实际的输出渲染代码,我得到以下结果:
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 。

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

此测试的硬件是:
英特尔酷睿 I7-3930(规范: CPU breifmore detailedreally detailed spec)使用总共 32 GB 的 DDR3-1600 内存。

开放问题
  • 我在上述假设中是否正确?
  • 有没有办法增加内存带宽的使用? 例如,通过在 C/C++ 中执行它并在堆上更多地分散内存分配,从而可以使用所有四个内存 channel 。
  • 有没有更好的方法来测量内存数据传输?

  • 非常有义务就此提供意见。我知道这是一个复杂的领域......

    此处的所有代码均可从 https://github.com/Toby999/ThroughputTest 下载.请随时通过转发电子邮件 tobytemporary[at]gmail.com 与我联系。

    最佳答案

    如果您没有在内存中线性跨步,则增加步长时吞吐量的下降可能是由于内存预取不再正常工作造成的。

    你可以做的事情来提高速度:

  • 测试速度将受到循环本身占用 CPU 周期的人为限制。正如 Roy 所示,展开循环可以实现更高的速度。
  • 您应该摆脱边界检查(使用“未检查”)
  • 而不是使用 Parallel.For , 使用 Thread.Start并将您启动的每个线程固定在单独的核心上(使用此处的代码:Set thread processor affinity in Microsoft .Net)
  • 确保所有线程同时启动,这样你就不会测量任何落后者(你可以通过在所有线程运行和旋转时将 Interlock.Exchange 的内存地址旋转到一个新值来做到这一点)
  • 在 NUMA 机器(例如 2 Socket Modern Xeon)上,您可能需要采取额外的步骤来在线程所在的 NUMA 节点上分配内存。为此,您需要 PInvoke VirtualAllocExNuma
  • 说到内存分配,使用大页面应该会提供另一个提升

  • 虽然 .NET 不是用于此类测试的最简单框架,但可以诱使它执行您想要的操作。

    关于c# - 如何最大化DDR3内存数据传输率?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20554123/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com