C# 有效主存到CPU的最大带宽(C)#

C# 有效主存到CPU的最大带宽(C)#,c#,ram,bandwidth,C#,Ram,Bandwidth,我想写一个C#程序,能够对从主存读取的数据执行基本操作,以便尽可能接近主存的读取带宽 我想我们可以确定在使用非常大的阵列时不会使用缓存。到目前为止,使用多线程和long[]时,我从未能够跨越2 GB/s的限制,而我知道现代RAM带宽至少是10 GB/s。(我有一台现代化的计算机,以64位运行,当然没有调试的发布模式) 你能提供一个接近最大带宽的C#程序吗?如果不是,你能解释一下为什么C#程序不能做到这一点吗 例如: 准备:创建一个(几个?)大数组并用随机数填充 主步骤:对数组中的所有元素求和(

我想写一个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));
}
uint[]数据=新uint[10000000*32];
对于(int j=0;j<15;j++)
{
单位和=0;
var sw=Stopwatch.StartNew();
对于(uint i=0;i
在我的机器上,我得到了大约19.8-20.1 GB/s,我知道单线程带宽应该在20 GB/s左右,所以这看起来不错。我的机器上的多线程带宽实际上更高,大约30 GB/s,但这需要一个更复杂的测试,至少协调两个线程

在这个基准测试中需要一些技巧。最重要的是,我依靠64字节的缓存线来跳过对大部分数据的任何操作。由于代码确实会触及每个缓存线(由于阵列不一定是64对齐的,所以在开始和结束时可能会减去一到两条缓存线),因此整个阵列将从内存中传输。为了以防万一(它确实稍微改变了结果,所以我保留了它),我将循环展开4,并使索引变量无符号,以避免无意义的
movsx
指令。保存操作非常重要,特别是对于这样的标量代码,这样可以避免使其成为瓶颈,而不是内存带宽


但是,这并不能真正地对系统可用的总内存带宽进行基准测试,在我的系统上,单核是不可能做到这一点的。某些微体系结构细节可以将单个内核的内存带宽限制为小于整个处理器的总内存带宽。您可以阅读by BeeOnRope中的各种详细信息。

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

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));
}
uint[]数据=新uint[10000000*32];
对于(int j=0;j<15;j++)
{
单位和=0;
var sw=Stopwatch.StartNew();
对于(uint i=0;i
在我的机器上,我得到了大约19.8-20.1 GB/s,我知道单线程带宽应该在20 GB/s左右,所以这看起来不错。我的机器上的多线程带宽实际上更高,大约30 GB/s,但这需要一个更复杂的测试,至少协调两个线程

在这个基准测试中需要一些技巧。最重要的是,我依靠64字节的缓存线来跳过对大部分数据的任何操作。由于代码确实会触及每个缓存线(由于阵列不一定是64对齐的,所以在开始和结束时可能会减去一到两条缓存线),因此整个阵列将从内存中传输。为了以防万一(它确实稍微改变了结果,所以我保留了它),我将循环展开4,并使索引变量无符号,以避免无意义的
movsx
指令。保存操作非常重要,特别是对于这样的标量代码,这样可以避免使其成为瓶颈,而不是内存带宽


但是,这并不能真正地对系统可用的总内存带宽进行基准测试,在我的系统上,单核是不可能做到这一点的。某些微体系结构细节可以将单个内核的内存带宽限制为小于整个处理器的总内存带宽。您可以在by BeeOnRope中阅读各种详细信息。

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

for循环读取16个元素中的一个元素达到多线程带宽。但实际上,读取所有元素的基本循环离它不远了,因为在多线程版本中,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));
}
int N=64;
uint[][]数据=新uint[N][];
对于(int k=0;k
有关我的笔记本电脑上的测量信息:

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