Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/282.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么在2048x2048和2047x2047阵列乘法中会有巨大的性能损失?_C#_Arrays_Matrix Multiplication - Fatal编程技术网

C# 为什么在2048x2048和2047x2047阵列乘法中会有巨大的性能损失?

C# 为什么在2048x2048和2047x2047阵列乘法中会有巨大的性能损失?,c#,arrays,matrix-multiplication,C#,Arrays,Matrix Multiplication,我正在做一些矩阵乘法基准测试,如前所述 现在我有另一个问题,当两个2048x2048矩阵相乘时,C#和其他矩阵之间有很大的区别。当我尝试只乘以2047x2047矩阵时,这似乎很正常。还添加了一些其他的用于比较 1024x1024-10秒 1027x1027-10秒 2047x2047-90秒 2048x2048-300秒 2049x2049-91秒。(更新) 2500x2500-166秒 对于2k乘2k的情况,这是三分半钟的差异 使用2dim阵列 //Array init like this

我正在做一些矩阵乘法基准测试,如前所述

现在我有另一个问题,当两个2048x2048矩阵相乘时,C#和其他矩阵之间有很大的区别。当我尝试只乘以2047x2047矩阵时,这似乎很正常。还添加了一些其他的用于比较

1024x1024-10秒

1027x1027-10秒

2047x2047-90秒

2048x2048-300秒

2049x2049-91秒。(更新)

2500x2500-166秒

对于2k乘2k的情况,这是三分半钟的差异

使用2dim阵列

//Array init like this
int rozmer = 2048;
float[,] matice = new float[rozmer, rozmer];

//Main multiply code
for(int j = 0; j < rozmer; j++)
{
   for (int k = 0; k < rozmer; k++)
   {
     float temp = 0;
     for (int m = 0; m < rozmer; m++)
     {
       temp = temp + matice1[j,m] * matice2[m,k];
     }
     matice3[j, k] = temp;
   }
 }
//像这样的数组初始化
int-rozmer=2048;
浮动[,]材质=新浮动[rozmer,rozmer];
//主乘法码
对于(int j=0;j
这可能与cpu缓存的大小有关。若矩阵的两行不匹配,那个么您将失去从RAM中交换元素的时间。额外的4095元素可能足以阻止行拟合

在您的例子中,2047个2d矩阵的2行位于16KB内存范围内(假设为32位类型)。例如,如果您有64KB的一级缓存(最接近总线上的cpu),那么您可以一次将至少4行(2047*32)放入缓存中。对于较长的行,如果需要任何填充来将行对推到16KB以上,那么事情就会变得一团糟。此外,每次您“错过”缓存时,从另一个缓存或主内存交换数据都会延迟一些事情


我的猜测是,不同大小的矩阵在运行时的差异受操作系统如何有效地利用可用缓存的影响(有些组合只是有问题)。当然,对我来说,这只是一个粗略的简化。

可能是一种缓存效果。由于矩阵维数是2的幂次大,而缓存大小也是2的幂次大,因此最终只能使用一级缓存的一小部分,从而大大降低了速度。原始矩阵乘法通常受到将数据提取到缓存的需要的限制。使用平铺(或缓存遗忘算法)的优化算法侧重于更好地利用一级缓存

如果你对其他对(2^n-1,2^n)计时,我希望你会看到类似的效果


为了更全面地解释,在访问matice2[m,k]的内部循环中,matice2[m,k]和matice2[m+1,k]可能相互偏移2048*sizeof(float),从而映射到一级缓存中的相同索引。对于N路关联缓存,所有这些缓存通常都有1-8个缓存位置。因此,几乎所有这些访问都将触发一级缓存逐出,并从较慢的缓存或主内存中提取数据。

您似乎已达到缓存大小限制,或者可能在计时中存在一些重复性问题


无论问题是什么,您都不应该自己用C#编写矩阵乘法,而应该使用BLAS的优化版本。在任何现代机器上,矩阵的大小都应该在一秒钟内相乘。

有效利用缓存层次结构非常重要。您需要确保多维数组中的数据排列整齐,这可以通过平铺来实现。要做到这一点,您需要将2D数组作为1D数组与索引机制一起存储。传统方法的问题是,虽然同一行中的两个相邻数组元素在内存中相邻,但同一列中的两个相邻元素将在内存中被W个元素分隔,其中W是列数。平铺可以产生多达十倍的性能差异

如果时间在较大的大小时下降,那么缓存冲突的可能性是否会更大,特别是对于有问题的矩阵大小,使用2的幂不是更大?我不是缓存问题的专家,但在缓存相关的性能问题上有很好的信息

当您垂直访问
matice2
数组时,它将在缓存中进行更多的交换。如果沿对角线镜像阵列,以便可以使用
[k,m]
而不是
[m,k]
访问它,则代码运行速度会快得多


我对1024x1024矩阵进行了测试,速度大约是原来的两倍。对于2048x2048矩阵,速度大约快十倍。

我怀疑这是一种称为“顺序泛洪”的结果。这是因为您正试图在略大于缓存大小的对象列表中循环,因此对列表(数组)的每个请求都必须从ram中完成,并且不会得到一个缓存命中


在您的例子中,您在阵列2048索引中循环2048次,但您只有2047的空间(可能是由于阵列结构的一些开销),因此每次访问阵列pos时,它都需要从ram中获取该阵列pos。然后它被存储在缓存中,但在再次使用之前,它被转储。因此缓存基本上是无用的,导致执行时间更长

这可能与二级缓存中的冲突有关

matice1上的缓存未命中不是问题,因为它们是按顺序访问的。 但是,对于matice2,如果一个完整的列适合二级缓存(即当您访问matice2[0,0]、matice2[1,0]、matice2[2,0]……等时,没有任何内容被逐出),那么matice2的缓存未命中也没有问题

现在更深入地了解缓存的工作原理,如果变量的字节地址是X,那么它的缓存线将是(X>>6)和(L-1)。其中L是缓存中缓存线的总数。L总是2的幂。 这六个是由2^6==64乘以