我一直在试图找到使用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;
}
有一些东西我在指南里找不到,主要涉及值的加载和存储。
我知道有一些新的指令可以同时处理更多量的数据,但这个初步实现应该可以在旧芯片上运行。
对于这个示例,我也忽略了对齐和缓冲区溢出的潜在问题,因为我认为这超出了问题的范围。
_mm_div_epi16
是一个库函数,而不是硬件指令的内部函数。即使AVX512也没有SIMD整数除法,只有浮点除法。英特尔的内部函数指南混淆地列出了他们的SVML库函数和真实指令的内部函数,可能是为了诱使人们购买他们的库。您可以使用左侧的复选框过滤掉它。 - Peter Cordes__m128i vzero = _mm_setzero_si128();
或_mm_set1_epi32(0)
。不要在 C 源代码中尝试进行 xor-zero,让编译器为您进行优化。 - Peter Cordes