计算大点积的最快方法是啥?

Posted

技术标签:

【中文标题】计算大点积的最快方法是啥?【英文标题】:What is the fastest way to compute large dot products?计算大点积的最快方法是什么? 【发布时间】:2013-06-09 16:25:11 【问题描述】:

考虑一下这个sn-p:

double dot(double* a, double* b, int n) 
  double sum = 0;
  for (int i = 0; i < n; ++i) sum += a[i] * b[i];
  return sum;

如何使用内部函数或汇编程序加快速度?

注意事项:

您可以采用最新架构,包括 AVX 扩展。 n 是几百个。 dot 本身将用于紧密循环

【问题讨论】:

我很惊讶你的编译器在一个已经很小的函数上做得并不好。您能否展示您当前的输出程序集,以便我们有一个起点? 如果您使用的是超线程内核,您可以在两个线程之间拆分工作吗?我不知道那会不会给你买很多东西。 你现在使用什么编译器和选项? 你是在重复取两个向量的点积吗?当您调用dot 时,向量在内存层次结构中的哪个位置? 【参考方案1】:

这是一个简单的 SSE 实现:

#include "pmmintrin.h"

__m128d vsum = _mm_set1_pd(0.0);
double sum = 0.0;
int k;

// process 2 elements per iteration
for (k = 0; k < n - 1; k += 2)

    __m128d va = _mm_loadu_pd(&a[k]);
    __m128d vb = _mm_loadu_pd(&b[k]);
    __m128d vs = _mm_mul_pd(va, vb);
    vsum = _mm_add_pd(vsum, vs);


// horizontal sum of 2 partial dot products
vsum = _mm_hadd_pd(vsum, vsum);
_mm_store_sd(&sum, vsum);

// clean up any remaining elements
for ( ; k < n; ++k)

    sum += a[k] * b[k];

请注意,如果您可以保证 a 和 b 是 16 字节对齐的,那么您可以使用 _mm_load_pd 而不是 _mm_loadu_pd,这可能有助于提高性能,尤其是在较旧的(Nehalem 之前的)CPU 上。

另请注意,对于这样的循环,相对于负载数量而言,算术指令非常少,那么性能很可能会受到内存带宽的限制,并且在实践中可能无法实现矢量化的预期加速。


如果您想使用 AVX 来定位 CPU,那么从上述 SSE 实现到每次迭代处理 4 个元素而不是 2 个元素是相当简单的转换:

#include "immintrin.h"

__m256d vsum = _mm256_set1_pd(0.0);
double sum = 0.0;
int k;

// process 4 elements per iteration
for (k = 0; k < n - 3; k += 4)

    __m256d va = _mm256_loadu_pd(&a[k]);
    __m256d vb = _mm256_loadu_pd(&b[k]);
    __m256d vs = _mm256_mul_pd(va, vb);
    vsum = _mm256_add_pd(vsum, vs);


// horizontal sum of 4 partial dot products
vsum = _mm256_hadd_pd(_mm256_permute2f128_pd(vsum, vsum, 0x20), _mm256_permute2f128_pd(vsum, vsum, 0x31));
vsum = _mm256_hadd_pd(_mm256_permute2f128_pd(vsum, vsum, 0x20), _mm256_permute2f128_pd(vsum, vsum, 0x31));
_mm256_store_sd(&sum, vsum);

// clean up any remaining elements
for ( ; k < n; ++k)

    sum += a[k] * b[k];

【讨论】:

很好的答案。 “如果您可以保证 a 和 b 是 16 字节对齐的,那么您可以使用 _mm_load_pd 而不是 _mm_loadu_pd,这可能有助于提高性能,尤其是在较旧的(Nehalem 之前的)CPU 上。”我认为即使在现代 CPU(至少到 Ivy Bridge)上,对齐也很重要。自 Nehalem 以来的唯一区别是,现在在对齐内存上的加载速度几乎与 loadu 一样快,但在未对齐内存上的加载速度仍然慢得多。 @raxman: 是的,即使在 Nehalem 和更高版本的 CPU 上仍然存在可测量的差异,但与旧 CPU 上未对齐的加载/存储的典型 2 倍命中相比,差异相对较小,而且对于微不足道的诸如上述的操作,其中内存带宽可能是限制因素,它可能是微不足道的。但是,是的,请始终尽可能使用 16 字节对齐。 hadd_pd 不单独横向添加 128 位部分吗?必须有一个 permute2f128(vsum,vsum,1) 来切换两个加法之间的高位部分吗? 为什么工程师不制作任何能够添加所有元素的avx指令?例如,_mm256_dp_ps 因为适合 r,g,b,a 或 x,y,z,0 的乘法,所以做两个单独的点积? @huseyin:确实 - 看起来 AVX 实际上只是两个 SSE 执行单元用螺栓固定在一起 - 这对于大多数基本的 SIMD 操作都很好,但是当您进行数据扩展/缩小操作(打包、解包等)时) 或需要在向量上水平操作(水平相加、对齐器等),那么您会遇到问题,您必须开始排列向量以使事情正常工作,这会降低整体效率。

以上是关于计算大点积的最快方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

计算 32 次方对数的最快方法是啥?

计算用户平均速度的最快方法是啥?

用二维窗口计算滚动函数的最快方法是啥?

计算行列式的最快方法是啥?

计算R中前两个主成分的最快方法是啥?

获取计算列表中前 n 个项目的最快方法是啥?