C# 中有效主内存到 CPU 的最大带宽



我想编写一个能够对从主内存读取的数据运行基本操作的 C# 程序,以便我可以尽可能接近主内存读取带宽

我想我们可以确保在使用非常大的数组时不使用缓存。到目前为止,使用多个线程和 long[] 我从未能够超过 2 GB/s 秒的限制,而我知道现代 RAM 带宽至少更像是 10 GB/s。(我有一台现代计算机,以 64 位运行,当然无需调试即可发布模式)。

你能提供一个能够接近最大带宽的 C# 程序吗?如果不是,你能解释为什么C#程序不能做到这一点吗?

例如:

  • 准备:创建一个(几个?)大数组并用随机数填充它
  • 主要步骤:对数组中的所有元素求和(或任何低 CPU 操作)

假设你的意思是单线程带宽,这相当容易,例如这样:

uint[] data = new uint[10000000 * 32];
for (int j = 0; j < 15; j++)
{
uint sum = 0;
var sw = Stopwatch.StartNew();
for (uint i = 0; i < data.Length; i += 64)
{
sum += data[i] + data[i + 16] + data[i + 32] + data[i + 48];
}
sw.Stop();
long dataSize = data.Length * 4;
Console.WriteLine("{0} {1:0.000} GB/s", sum, dataSize / sw.Elapsed.TotalSeconds / (1024 * 1024 * 1024));
}

在我的机器上,我从中获得大约 19.8-20.1 GB/s,我知道单线程带宽应该在 20 GB/s 左右,所以这看起来不错。我机器上的多线程带宽实际上更高,大约 30 GB/s,但这需要更复杂的测试来协调至少两个线程。

在此基准测试中需要一些技巧。最重要的是,我依靠 64 字节的缓存行大小来跳过对大多数数据执行任何操作。由于代码确实触及每个缓存行(由于数组不一定是 64 对齐的,因此在开头和结尾减去一两个),整个数组将从内存中传输。以防万一(它确实稍微改变了结果,所以我保留了它),我将循环展开了 4,并使索引变量无符号以避免毫无意义的movsx指令。保存操作很重要,尤其是对于像这样的标量代码,以避免使其成为瓶颈,而不是内存带宽。

但是,这并不能真正对系统可用的总内存带宽进行基准测试,这在我的系统上无法从单个内核实现。某些微架构细节可以将单个内核的内存带宽限制为小于整个处理器的总内存带宽。您可以在BeeOnRope的回答中阅读各种详细信息。

这是遵循@harold(非常好)答案的多线程版本。

for 循环读取 16 个元素中的一个达到多 trhead 带宽。但实际上基本的 for 循环读取所有元素离它不远,因为 CPU 瓶颈在多线程版本中不是问题。

int N = 64;
uint[][] data = new uint[N][];
for (int k = 0; k < N; k++)
{
data[k] = new uint[1000000 * 32];
}
for (int j = 0; j < 15; j++)
{
long total = 0;
var sw = Stopwatch.StartNew();
Parallel.For(0, N, delegate (int k)
{
uint sum = 0;
uint[] d = data[k];
//for (uint i = 0; i < d.Length; i += 64)
//{
//    sum += d[i] + d[i + 16] + d[i + 32] + d[i + 48];
//}
for (uint i = 0; i < d.Length; i++)
{
sum += d[i];
}
Interlocked.Add(ref total, sum);
});
sw.Stop();
long dataSize = (long)data[0].Length* N * 4;
Console.WriteLine("{0} {1:0.000} GB/s", total, dataSize / sw.Elapsed.TotalSeconds / (1024 * 1024 * 1024));
}

有关笔记本电脑上的信息测量:

  • 单线程带宽:13 GB/秒
  • 多线程带宽:20 GB/秒
  • 多线程读取所有元素:17 GB/s

最新更新