【发布时间】:2013-12-31 11:47:15
【问题描述】:
我正在尝试通过测试来测量 DDR3 内存数据传输率。根据 CPU 规格。最大理论带宽为 51.2 GB/s。这应该是四个通道的组合带宽,即 12.8 GB/通道。然而,这是一个理论上的限制,我很好奇如何进一步提高这篇文章中的实际限制。在下面描述的测试场景中,我实现了约 14 GB/s 的数据传输率,我相信这可能是在消除 CPU L1、L2 和 L3 缓存的大部分吞吐量提升时的近似值。
2014 年 3 月 20 日更新: 这种杀死 L1-L3 缓存的假设是错误的。内存控制器的硬件预取将分析数据访问模式,由于它是顺序的,因此将数据预取到 CPU 缓存中很容易。
具体问题在底部,但主要是我感兴趣的是 a) 验证导致此结果的假设,以及 b) 是否有更好的方法来测量 .NET 中的内存带宽。
我已经在 .NET 上用 C# 构建了一个测试作为初学者。尽管从内存分配的角度来看 .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;
}
这是实际测试:
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 缓存会强制更多缓存未命中,部分原因可能是内存控制器预取机制在读取相距太远时无法正常工作。我进一步相信第 1000 步的结果是最接近实际实际内存速度的结果,因为它应该会杀死大部分 CPU 缓存并“希望”杀死预取机制。此外,我假设此循环中的大部分开销是内存获取操作,而不是其他。
此测试的硬件是: Intel Core I7-3930(规格:CPU breif、more detailed 和 really detailed spec)使用 32 GB DDR3-1600 内存。
开放式问题
我的上述假设是否正确?
有没有办法增加内存带宽的使用?例如,改为在 C/C++ 中进行,并在堆上分散内存分配,使所有四个内存通道能够可以使用。
有没有更好的方法来衡量内存数据传输?
非常有义务为此提供意见。我知道这是一个复杂的领域......
这里的所有代码都可以在https://github.com/Toby999/ThroughputTest 下载。请随时通过转发电子邮件与我联系 tobytemporary[at]gmail.com。
【问题讨论】:
-
好问题,如果它有一些代码,其中包含您尝试过的内容、预期内容以及实际得到的内容。
-
@Prashant:我认为预期/实际得到的已经存在(51.2GB/s vs. ~10GB/s)。
-
@Oli Charlesworth 啊,对。所以只是代码。
-
您将很难使用 .NET 实现全部内存带宽。通常这是为那些使用 SIMD 的人保留的,.NET 不提供任何访问权限。
-
我刚刚在 C++ 中实现了一个 SSE 实现,作为这个测试项目的一部分。但是无论平台如何,内存带宽利用率仍然很有趣/重要的是要了解更多信息。也许将相同的测试转换为 C++ 会带来更好的信息和更多的可能性。这是第二个问题。 :)
标签: c# .net memory memory-management ram