【发布时间】:2021-12-15 14:05:49
【问题描述】:
我正在研究神经网络的矩阵向量乘法+累加函数,我最终决定手动对整个事物进行向量化,而不是依赖自动向量化。
我想出了这个功能:
#include <immintrin.h>
#define re *restrict //just simplification
// computes a2[n2]=w[n2][n1]*a1[n1]+b[n2]
void l_frw(const int n2,const int n1,float re a2,const float re a1,const float w[restrict][n1],const float re b)
{
__m256 x,y,z;
__m256 one=_mm256_set1_ps(1.0f);
for(int i=0; i<n2; i++)
{
a2[i]=b[i];
z=_mm256_setzero_ps();
for(int j=0; j<n1; j+=8)
{
x=_mm256_loadu_ps(&a1[j]);
y=_mm256_loadu_ps(&w[i][j]);
z=_mm256_fmadd_ps(x,y,z); //accumulates dot product of each row into z
}
z=_mm256_dp_ps(z,one,0b11111111);
a2[i]+=z[0]+z[4];
}
}
(是的,它只适用于 8 个大小的向量的倍数)。
它比简单的自动矢量化版本快约 20%,这非常简洁,但我仍在寻找改进。 有关如何加快速度的任何建议?
【问题讨论】:
-
您的 memcpy 调用有问题 - 它需要以字节为单位的大小。好像也是多余的?
-
我认为@chtz 只是意味着通过流水线和乱序执行进行并行化。如果您连续编写多个不依赖于彼此结果的指令,机器可以同时执行它们。因此,展开
i上的循环并从各行交错fmadd操作。 -
通过使用 stackoverflow.com/a/13222410/15671081 而不是 dp_ps 获得了额外 2% 的加速。
-
请参阅Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables? (Unrolling FP loops with multiple accumulators) re:@NateEldredge 的建议,以及我的答案顶部的一些实际示例的链接,以及向量点积的延迟与吞吐量瓶颈讨论在那个答案中。使用多个累加器至关重要:您要尽量减少的不是循环开销,而是通过一个累加器的 FMA 操作链的延迟。
-
我不希望展开会减慢迭代速度,除非只是做错了......主要的一点是只加载一次
x;第二点是洗牌矩阵w,这样你就可以线性地阅读它。第三点是检查n1是否足够小,可以提前加载整个向量x。
标签: c matrix-multiplication simd avx