SIMD - > uint16_t数组浮点数浮点数然后回到uint16_t

时间:2017-09-01 14:23:40

标签: c++ linux simd avx avx2

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

使用 for loops 的代码是

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上使用英特尔编译器2017 for parallel studio专业版2117(Fedora 25)。

1 个答案:

答案 0 :(得分:1)

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

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

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