支持 SIMD 的代码?

Posted

技术标签:

【中文标题】支持 SIMD 的代码?【英文标题】:SIMD-able code? 【发布时间】:2013-01-10 12:40:11 【问题描述】:

什么代码可以使用 SIMD 指令集的严格定义是什么?是否可以并行运行计算?

如果我有:

for(int i=0; i<100; i++)
    sum += array[i];

这可以利用 SIMD,因为我们可以运行:

for(int i=0; i<100;i=i+4)
    sum0 += array[i];
    sum1 += array[i+1];
    sum2 += array[i+2];
    sum3 += array[i+3];


sum = sum0 + sum1 + sum2 + sum3;

?

它必须是浮点类型,还是可以是双精度和整数?

【问题讨论】:

没有真正有用的定义。你可以在可以使用的时候使用它——这真的只能通过经验来回答。 【参考方案1】:

假设您谈论的是 x86 (SSE et al),那么支持的算术类型是 8、16、32 和 64 位整数,以及单精度和双精度浮点数。但请注意,并非所有数据类型都支持所有算术运算 - SSE 在这方面缺乏正交性。

假设 32 位整数和适当对齐的数组(16 字节对齐),那么您可以将上述循环示例实现为:

#include <emmintrin.h>                     // SSE2 intrinsics

int32_t a[100] __attribute__ ((aligned(16)));
                                           // suitably aligned array

__m128i vsum = _mm_set1_epi32(0);          // init vsum =  0, 0, 0, 0 
for (int i = 0; i < 100; i += 4)

    __m128i v = _mm_load_si128(&a[i]);     // load 4 ints from a[i]..a[i+3]
    vsum = _mm_add_epi32(vsum, v);         // accumulate 4 partial sums

// final horizontal sum of partial sums
vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 8));
int32_t sum = _mm_cvtsi128_si32(vsum);     // sum = scalar sum of a[]

【讨论】:

感谢保罗。你能为我澄清一些事情吗?关于对齐,这是 SSE/AVX 扩展类型为程序员考虑的事情吗?或者这是我们必须做的事情,如果是这样,这是怎么做的?我想你指的是填充字节? 基本 SIMD 数据类型(例如 __m128i)会自动正确对齐,但对于非 SIMD 数据类型和动态内存分配(newmalloc 等),那么您需要额外注意以确保对齐。如果您的数据由于某种原因无法正确对齐,那么您可以使用未对齐的 SIMD 加载/存储作为最后的手段,但这会降低性能。 为什么要关注动态内存分配?我们不是只处理“原始”SSE 类型(“8、16、32 和 64 位整数,以及单精度和双精度浮点数”)吗? 根据目标平台,动态内存分配可能只有 4 或 8 字节对齐。对于与 SIMD 一起使用的数组等,我们通常需要 16 字节对齐。您可以使用例如在 Linux 上 memalignposix_memalign 为此。 Windows 和 Mac OS X 具有类似的专用功能。如果您需要使用 C++ 的 new 运算符,那么您将需要一个自定义分配器。 如果底层数据类型是 SIMD 类型,则静态分配是可以的,例如__m128i,但如果底层数据类型是 int32_t,它只有 4 字节自然对齐,那么仍然存在问题。我已经更新了上面的答案,以说明使用 GNU 扩展对非 SIMD 数组强制正确对齐。

以上是关于支持 SIMD 的代码?的主要内容,如果未能解决你的问题,请参考以下文章

演示代码在禁用优化的情况下未能显示 SIMD 速度快 4 倍

如何在不影响性能的情况下抽象 SIMD 代码以处理不同的数据类型

相对于标准代码测试 simd 加速的正确方法是啥

求幂的 SIMD 代码

解释 Metal 和 SIMD 中的不同类型

使用 SIMD 指令将代码转换为代码