SIMD -> uint16_t 数组到浮点数组在浮点上工作,然后返回到 uint16_t

Posted

技术标签:

【中文标题】SIMD -> uint16_t 数组到浮点数组在浮点上工作,然后返回到 uint16_t【英文标题】:SIMD -> uint16_t array to float array work on float then back to uint16_t 【发布时间】:2017-09-01 14:23:40 【问题描述】:

我目前正在从事一个处理图像的项目。为了加快这个过程(并增加我的知识),我决定使用 SIMD 指令编写一些基本功能。

使用for循环的代码是

int idx;
uint16_t* A, B, C;
float gAlpha = 0.8;
float alpha = 0.2;
for (size_t rw = 0; rw < height; rw++) 
   for (size_t cl = 0; cl < width; cl++) 
            idx = rw * width + height;
            C[idx] =  static_cast<uint16_t>(gAlpha * static_cast<float>(A[idx]) + alpha * static_cast<float>(B[idx]));
        
    

这个循环可能并不完美,但它完美地完成了它的工作,我的单元测试给了我预期的结果。

正如我所说,我正在尝试使用 SIMD 内在函数转换这些循环。这是我的工作代码,正如您将看到的那样,它不是很漂亮......我们确实可以访问 AVX2 的内部代码。

size_t n_pixels = height * width;
for (size_t px = 0; px < n_pixels; px += 8) 
    __m128i xlo = _mm_unpacklo_epi16(_mm_load_si128((__m128i*)&A[px]), _mm_set1_epi16(0));
    __m128i xhi = _mm_unpackhi_epi16(_mm_load_si128((__m128i*)&A[px]), _mm_set1_epi16(0));
    __m128 ylo = _mm_cvtepi32_ps(xlo);
    __m128 yhi = _mm_cvtepi32_ps(xhi);
    __m256 pxMinFl = _mm256_castps128_ps256(ylo);
    pxMinFl = _mm256_insertf128_ps(pxMinFl, yhi, 1);

    xlo = _mm_unpacklo_epi16(_mm_load_si128((__m128i*)&B[px]), _mm_set1_epi16(0));
    xhi = _mm_unpackhi_epi16(_mm_load_si128((__m128i*)&B[px]), _mm_set1_epi16(0));
    ylo = _mm_cvtepi32_ps(xlo);
    yhi = _mm_cvtepi32_ps(xhi);
    __m256 pxMaxFl = _mm256_castps128_ps256(ylo);
    pxMaxFl = _mm256_insertf128_ps(pxMaxFl, yhi, 1);

    __m256 avGain1 = _mm256_set1_ps(gAlpha);
    __m256 avGain2 = _mm256_set1_ps(alpha);

    __m256 prodUp = _mm256_mul_ps(prodUp, avGain1);
    __m256 prodBt = _mm256_mul_ps(prodBt, avGain2);
    __m256 pxOutFl = _mm256_add_ps(prodUp, prodBt);

    __m128 ylo_ps = _mm256_castps256_ps128(pxOutFl);
    __m128 yhi_ps = _mm256_extractf128_ps(pxOutFl, 1);
    __m128i xlo_ep = _mm_cvtps_epi32(ylo_ps);
    __m128i xhi_ep = _mm_cvtps_epi32(yhi_ps); <- POINT 1

    int* xl = reinterpret_cast<int*>(&xlo_ep); <- POINT 2
    for (int i=0; i < 8; i++)                 <- POINT 2
        C[px + i] = static_cast<uint16_t>(xl[i]); <- POINT 2
    

可能对这段代码进行了大量优化,但我已经检查了 pxOutFl 的输出是否符合预期值。当我查看如何将数据保存回输出数组 C 时,对我来说开始看起来像黑魔法的地方。首先,如果我在 POINT 1 处注释该行,则 代码不起作用 即使如您所见,我不使用该变量。其次,我猜想有一个比我用来将数据存储回 uint16_t 数组 (POINT 2) 的技巧更好的解决方案,但我找不到一个有效的解决方案。

有人能指出我正确的方向吗?我错过了什么?我该如何改进这段代码?

提前致谢!

PS:我们在 Linux (Fedora 25) 上使用 Intel compiler 2017 for parallel studio Professional edition 2117。

【问题讨论】:

这样的混合在定点上很容易,实际上更容易,因为不会改变车道宽度。 【参考方案1】:

您可以将所有 POINT 2 重写为:

_mm_storeu_si128((__m128i *)&C[px], xlo_ep);

还请注意,_mm_load_si128 的所有实例可能都应该是 _mm_loadu_si128,因为您似乎无法保证任何地方的对齐。

【讨论】:

以上是关于SIMD -> uint16_t 数组到浮点数组在浮点上工作,然后返回到 uint16_t的主要内容,如果未能解决你的问题,请参考以下文章

从uint32_t [16]数组到uint32_t变量序列的64位副本

动态分配 SIMD 向量数组是不是安全?

将位间隔从变量复制到数组

从字节数组加载 uint8x16_t 的对齐要求?

将 char 数组转换为 uint16_t 数组 C/C++

从 Keil 中的 uint8_t 数组获取 uint16_t 值