我可以加速超过 _mm256_i32gather_epi32

can i speed up more than _mm256_i32gather_epi32

我做了4k视频的伽马转换代码

 /** gamma0
     input range   : 0 ~ 1,023
     output range  : 0 ~ ?
   */
    v00 = _mm256_unpacklo_epi16(v0, _mm256_setzero_si256());
    v01 = _mm256_unpackhi_epi16(v0, _mm256_setzero_si256());
    v10 = _mm256_unpacklo_epi16(v1, _mm256_setzero_si256());
    v11 = _mm256_unpackhi_epi16(v1, _mm256_setzero_si256());
    v20 = _mm256_unpacklo_epi16(v2, _mm256_setzero_si256());
    v21 = _mm256_unpackhi_epi16(v2, _mm256_setzero_si256());

    v00 = _mm256_i32gather_epi32(csv->gamma0LUT, v00, 4);
    v01 = _mm256_i32gather_epi32(csv->gamma0LUT, v01, 4);
    v10 = _mm256_i32gather_epi32(csv->gamma0LUTc, v10, 4);
    v11 = _mm256_i32gather_epi32(csv->gamma0LUTc, v11, 4);
    v20 = _mm256_i32gather_epi32(csv->gamma0LUTc, v20, 4);
    v21 = _mm256_i32gather_epi32(csv->gamma0LUTc, v21, 4);

我想实现“10 位输入到 10~13 位输出”LUT(查找 table),但 AVX2 仅支持 32 位命令。

所以,不可避免地扩展到32位,并使用_mm256_i32gather_epi32命令实现。

这方面的性能瓶颈是最严重的,有什么办法可以改善吗?

由于你的问题的上下文对我来说仍然有点模糊,只是一些你可以尝试的一般想法(与你目前所拥有的相比,有些可能稍微好一些甚至更糟,下面的所有代码都未经测试):

具有 16 位值的 LUT 使用 _mm256_i32gather_epi32

即使它加载 32 位值,您仍然可以使用 2 的乘数作为 _mm256_i32gather_epi32 的最后一个参数。你应该确保你的 LUT 前后 2 个字节是可读的。

static const int16_t LUT[1024+2] = { 0, val0, val1, ..., val1022, val1023, 0};
__m256i high_idx = _mm256_srli_epi32(v, 16);
__m256i low_idx  = _mm256_blend_epi16(v, _mm256_setzero_si256(), 0xAA);

__m256i high_val = _mm256_i32gather_epi32((int const*)(LUT+0), high_idx, 2);
__m256i low_val  = _mm256_i32gather_epi32((int const*)(LUT+1), low_idx, 2);
__m256i values   = _mm256_blend_epi16(low_val, high_val, 0xAA);

将两个值合并为一个值LUT-entry

对于small-ish个LUT,可以从相邻的两个索引计算出一个索引为(idx_hi << 10) + idx_low,直接查找对应的元组。但是,在您的情况下,您将拥有 4 MiB LUT,而不是 2KiB,这可能会损害缓存——但您只有一半的收集指令。

多项式逼近

在数学上,有限区间上的所有连续函数都可以用多项式来逼近。您可以将您的值转换为 float 评估多项式并将其转换回来,或者直接使用 fixed-point 乘法(请注意 _mm256_mulhi_epi16/_mm256_mulhi_epu16 计算 (a * b) >> 16,如果一个因素实际上在 [0, 1).

中,这很方便

8 位,16 项线性插值 LUT

SSE/AVX2 提供了一条 pshufb 指令,可用作具有 16 个条目(和一个隐含的 0 条目)的 8 位 LUT。 Proof-of-concept 实施:

__m256i idx = _mm256_srli_epi16(v, 6);  // shift highest 4 bits to the right
idx =  _mm256_mullo_epi16(idx, _mm256_set1_epi16(0x0101)); // duplicate idx, maybe _mm256_shuffle_epi8 is better?
idx =  _mm256_sub_epi8(idx, _mm256_set1_epi16(0x0001)); // subtract 1 from lower idx, 0 is mapped to 0xff
__m256i lut_vals = _mm256_shuffle_epi8(LUT, idx);     // implicitly: LUT[-1] = 0

// get fractional part of input value:
__m256i dv = _mm256_and_si256(v, _mm256_set1_epi8(0x3f)); // lowest 6 bits
dv = _mm256_mullo_epi16(dv, _mm256_set1_epi16(0xff01));  // dv = [-dv, dv]
dv = _mm256_add_epi8(dv, _mm256_set1_epi16(0x4000));     // dv = [0x40-(v&0x3f), (v&0x3f)];
__m256i res = _mm256_maddubs_epi16(lut_vals, dv); // switch order depending on whether LUT values are (un)signed.
// probably shift res to the right, depending on the scale of your LUT values

您也可以将其与首先进行线性或二次近似并仅计算目标函数的差值相结合。