【问题标题】:Effective main memory to CPU maximum bandwidth in C#C#中有效主存到CPU最大带宽
【发布时间】:2018-05-06 01:29:45
【问题描述】:

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

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

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

例如:

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

【问题讨论】:

  • 您的意思是要获取内存详细信息还是我们在这里谈论基准测试?
  • Can you provide a C# program capable of Stackoverflow 不是代码编写服务,也不是教程网站。请阅读How to Ask 并采取tour
  • @Zack:我想对读取和使用 RAM 内容的程序进行基准测试。如果这是您的问题,这不适用于对某些特定 RAM 设备进行基准测试。它更多的是关于 C# 基准测试而不是 RAM 基准测试。
  • 我要求澄清,如果你的问题足够清楚并且包含很多资源,我不会这样做

标签: c# ram bandwidth


【解决方案1】:

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

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 的this answer 中阅读各种详细信息。

【讨论】:

  • 非常感谢。这正是我想知道的。它在我的电脑上以 13 GB/s 的速度运行。我需要给它更多的想法。我想知道多线程版本是否真的可以对数组的所有元素执行操作,同时保持接近最大带宽(因为应该有更少的 CPU 瓶颈)。我会解决这个问题的。
  • @BenoitSanchez System.Numerics.Vectors API 不是很强大,但也许它可以帮助您编写一些可以执行更多操作同时仍然最大化内存带宽的代码
  • 我的多线程带宽是 20 GB/s。不仅我可以达到它,而且实际上多线程版本能够读取数组中的所有元素而不会造成太大损失(比如大约 17 GB/s),即使使用原始 "for (uint i = 0; i
【解决方案2】:

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

从 16 个元素中读取一个元素的 for 循环达到了多头带宽。但实际上读取所有元素的基本 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/s
  • 多线程带宽:20 GB/s
  • 多线程读取所有元素:17 GB/s

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-22
    • 2013-11-21
    • 1970-01-01
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    • 2013-03-17
    • 1970-01-01
    相关资源
    最近更新 更多