我有一个二维的双精度数组,表示一个矩阵,它可能很大,例如200x200。
我需要能够有效地计算这个矩阵的和。我如何在C#中使用向量化来实现这一点?
当前的vanilla方法是:
double[,] matrix =
{
{ 0.0, 1, 2, 3 },
{ 4, 5, 6, 7 },
{ 8, 9, 10, 11 },
{ 12, 13, 14, 15 }
};
int rows = matrix.GetLength(0);
int cols = matrix.GetLength(1);
double sum = 0;
for (uint i = 0; i < rows; i++)
{
for (uint j = 0; j < cols; j++)
{
sum += matrix[i, j];
}
}
字符串
2条答案
按热度按时间dkqlctbz1#
这可以通过
System.Numerics
vector API很好地完成,至少可以自由使用Unsafe
类。据我所知,从二维矩阵中加载向量没有一个好的“标准”方法。普通加载的重载都不适用,也没有一个普通的方法来获得二维数组的
Span<T>
。但是使用Unsafe
,我们可以完成它。使用8个单独的数组展开8(参见Unrolling FP loops with multiple accumulators),并通过使用
Unsafe
操作引用将2D矩阵视为1D数组,我们可以做到这一点:(未测试,但在sharplab.io上编译)字符串
最后使用
Vector.Dot
来做水平求和有点傻,但是很短,而且只发生一次。在开始时,试图使地址对齐的循环主要是在AVX不使用时。不幸的是,这需要
unsafe
(关键字,而不是类),据我所知,即使原始指针立即转换为整数,并且从未用作指针。当AVX 2可用时(
Vector<T>
是128位的,没有AVX 2,即使你只使用float/double),主循环在汇编中可能看起来像这样:型
在我看来很好。我们可以通过直接比较地址来保存
add
,而不是保留冗余索引,但这不是一个大问题。csga3l582#
首先,你应该做一些基准测试和/或分析,问问自己这是否真的重要?求和是一个非常简单的计算,200 x200不是很大。我猜它可能需要一微秒的数量级,但这只是一个猜测。你还需要一个基准来决定你是否真的实现了 * 任何 * 改进,或者你只是让代码变得更复杂。
但这真的是应用程序的最大瓶颈吗?优化通常是为了避免重复工作。任何SIMD优化给予的最好效果是持续的加速。浪费时间优化对用户没有明显影响的函数是没有意义的。
如果你决定你需要优化,那么我会从摆脱索引计算开始。当你做
matrix[i, j]
时,框架本质上做了一个i * width + j
计算。这可能会比实际的求和时间更长。优化器可能会删除一些,但是我不会在没有实际确认的情况下从优化器中假设任何东西。你可以用fixed (double* ptr = matrix )
做不安全的路由,或者创建一个自定义矩阵类,它使用1D数组进行存储,只允许您使用单个循环对值求和,如果您出于其他原因需要[x, y]
语法,则可以自己实现2D索引器。如果你真的需要SIMD的性能,你可以从两个方面入手
Vector<T>
个1.内部特性,如Vector256
参见the comparison。简而言之,内在给予更好的性能,代价是将其绑定到特定的cpu平台。
在这两种情况下,你都需要了解内存布局来正确加载元素。但是一旦完成了,它应该非常简单,只需将所有向量加在一起,最后对元素求和。如果元素计数不能被向量长度整除,最后可能会使用一些标量代码。