我正开始使用 SIMD 编程,但我现在不知道该做什么。我试图减少运行时间,但它是以另一种方式进行的。
这是我的基本代码: https://codepaste.net/a8ut89
void blurr2(double * u, double * r) {
int i;
double dos[2] = { 2.0, 2.0 };
for (i = 0; i < SIZE - 1; i++) {
r[i] = u[i] + u[i + 1];
}
}
模糊2:0.43s
int contarNegativos(double * u) {
int i;
int contador = 0;
for (i = 0; i < SIZE; i++) {
if (u[i] < 0) {
contador++;
}
}
return contador;
}
负计数:1.38s
void ord(double * v, double * u, double * r) {
int i;
for (i = 0; i < SIZE; i += 2) {
r[i] = *(__int64*)&(v[i]) | *(__int64*)&(u[i]);
}
}
顺序:0.33
这是我的 SIMD 代码:
void blurr2(double * u, double * r) {
__m128d rp2;
__m128d rdos;
__m128d rr;
int i;
int sizeAux = SIZE % 2 == 1 ? SIZE : SIZE - 1;
double dos[2] = { 2.0, 2.0 };
rdos = *(__m128d*)dos;
for (i = 0; i < sizeAux; i += 2) {
rp2 = *(__m128d*)&u[i + 1];
rr = _mm_add_pd(*(__m128d*)&u[i], rp2);
*((__m128d*)&r[i]) = _mm_div_pd(rr, rdos);
}
}
模糊2:0.42s
int contarNegativos(double * u) {
__m128d rcero;
__m128d rr;
int i;
double cero[2] = { 0.0, 0.0 };
int contador = 0;
rcero = *(__m128d*)cero;
for (i = 0; i < SIZE; i += 2) {
rr = _mm_cmplt_pd(*(__m128d*)&u[i], rcero);
if (((__int64 *)&rr)[0]) {
contador++;
};
if (((__int64 *)&rr)[1]) {
contador++;
};
}
return contador;
}
负计数:1.42s
void ord(double * v, double * u, double * r) {
__m128d rr;
int i;
for (i = 0; i < SIZE; i += 2) {
*((__m128d*)&r[i]) = _mm_or_pd(*(__m128d*)&v[i], *(__m128d*)&u[i]);
}
}
阶数:0.35s
**不同的解决方案。
你能解释一下我做错了什么吗?我有点迷路了……
最佳答案
使用 _mm_loadu_pd
而不是指针转换和取消引用 __m128d
。您的代码保证在 gcc/clang 上出现段错误,其中 __m128d
被假定为对齐。
blurr2:乘以 0.5
而不是除以 2。它会更快很多。 (我在前一两天用完全相同的代码对一个问题发表了同样的评论,你也是吗?)
negativeCount: _mm_castpd_si128
比较结果为整数,用_mm_sub_epi64
累加。 (位模式是全零或全一,即 2 的补码 0/-1)。
#include <immintrin.h>
#include <stdint.h>
static const size_t SIZE = 1024;
uint64_t countNegative(double * u) {
__m128i counts = _mm_setzero_si128();
for (size_t i = 0; i < SIZE; i += 2) {
__m128d cmp = _mm_cmplt_pd(_mm_loadu_pd(&u[i]), _mm_setzero_pd());
counts = _mm_sub_epi64(counts, _mm_castpd_si128(cmp));
}
//return counts[0] + counts[1]; // GNU C only, and less efficient
// horizontal sum
__m128i hi64 = _mm_shuffle_epi32(counts, _MM_SHUFFLE(1, 0, 3, 2));
counts = _mm_add_epi64(counts, hi64);
uint64_t scalarcount = _mm_cvtsi128_si64(counts);
return scalarcount;
}
要了解有关高效 vector 水平和的更多信息,请参阅 Fastest way to do horizontal float vector sum on x86 .但第一个规则是在循环之外进行。
( source + asm on the Godbolt compiler explorer )
从 MSVC(我猜你正在使用它,否则你会从 *(__m128d*)foo
得到段错误),内部循环是:
$LL4@countNegat:
movups xmm0, XMMWORD PTR [rcx]
lea rcx, QWORD PTR [rcx+16]
cmpltpd xmm0, xmm2
psubq xmm1, xmm0
sub rax, 1
jne SHORT $LL4@countNegat
展开(可能还有两个 vector 累加器)可能会更快,但这相当不错,在 Sandybridge/Haswell 上可能接近每 16 字节 1.25 个时钟。 (5 个融合域 uops 的瓶颈)。
您的版本实际上是在内部循环内部解包为整数!如果您使用的是 MSVC -Ox
,它实际上是分支而不是使用无分支比较 + 条件添加。我很惊讶它并不比标量版本慢。
此外,(int64_t *)&rr
违反了严格的别名。 char*
可以为任何东西起别名,但将其他指针转换到 SIMD vector 上并期望它起作用是不安全的。如果是这样,你很幸运。编译器通常会为它或内在函数生成类似的代码,而且对于适当的内在函数通常不会更糟。
关于c++ - SIMD 程序运行缓慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47641724/