【发布时间】:2021-01-17 18:13:03
【问题描述】:
我一直在尝试找出使用 AMD64 SIMD 指令来实现 lerp 以用于大量 u8 值的最佳方法,但我似乎无法在不需要所有 SIMD 扩展的情况下找出正确的指令。
我现在使用的公式是
u8* a;
u8* b;
u8* result;
size_t count;
u16 total;
u16 progress;
u32 invertedProgress = total - progress;
for(size_t i = 0; i < count; i++){
result[i] = (u8)((b[i] * progress + a[i] * invertedProgress) / total);
}
我认为它看起来像:
u8* a;
u8* b;
u8* result;
size_t count;
u16 total;
u16 progress;
__m128i mmxZero;
__m128i mmxProgress;
__m128i mmxInvertedProgress;
__m128i mmxProductA;
__m128i mmxProductB;
mmxZero = _mm_xor_ps(zero, zero); // Is there a clear?
mmxProgress = Fill with progress;
mmxTotal = Fill with total;
mmxInvertedProgress = mmxTotal;
mmxInvertedProgress = _mm_unpacklo_epi8(mmxInvertedProgres, mmxZero);
mmxInvertedProgress = _mm_sub_epi8(mmxTotal, progress);
for(size_t i = 0; i < count; i += 8){
mmxProductA = load A;
// u8 -> u16
mmxProductA = _mm_unpacklo_epi8(mmxProductA, mmxZero);
mmxProductB = load B;
// u8 -> u16
mmxProductB = _mm_unpacklo_epi8(mmxProductB, mmxZero);
// a * (total - progress)
mmxProductA = _mm_mullo_epi16(mmxProductA, mmxInvertedProgress);
// b * progress
mmxProductB = _mm_mullo_epi16(mmxProductB, mmxProgress);
// a * (total - progress) + b * progress
mmxProductA = _mm_add_epi16(mmxProductA, mmxProductB);
// (a * (total - progress) + b * progress) / total
mmxProductA = _mm_div_epi16(mmxProductA, mmxTotal);
mmxProductA = saturated u16 -> u8;
store result = maxProductA;
}
这里有几件事我似乎无法在 in the guide 周围找到,主要与加载和存储值有关。
我知道有一些更新的指令可以同时执行更多的操作,这个初始实现应该适用于旧芯片。
对于这个例子,我也忽略了对齐和缓冲区溢出的可能性,我认为这有点超出了问题的范围。
【问题讨论】:
-
_mm_div_epi16是一个库函数,而不是硬件指令的内在函数。甚至 AVX512 也没有 SIMD 整数除法,只有浮点除法。英特尔的内在函数指南令人困惑地列出了他们的 SVML 库函数以及用于实际指令的内在函数,可能是为了诱使人们购买他们的库。您可以使用左侧的复选框将其过滤掉。 -
另外,MMX 早于 SSE / SSE2,并且只有 64 位整数向量。我建议使用变量名称,例如
__m128i vzero = _mm_setzero_si128();或_mm_set1_epi32(0)。不要试图在你的 C 源代码中进行异或零,让编译器为你做优化。 -
老实说,我没有找到 set 内在函数,我只是这样做,因为这是我找到归零的唯一方法。该页面上有很多内在函数!
标签: x86-64 sse simd intrinsics lerp