SSE 中的高效 min() 函数

Efficient min() function in SSE

本文关键字:函数 min 高效 SSE      更新时间:2023-10-16

我有以下循环,它取数组中每个条目的平方根:

#include <mmintrin.h>
float array[SIZE];
for (int i = 0; i < SIZE; i += 4)
{
    __m128 fourFloats, fourRoots;
    fourFloats = _mm_load_ps(&array[i]);
    fourRoots = _mm_sqrt_ps(fourFloats);
    float results[4];
    _mm_store_ps(results, fourRoots);
    // This is bottleneck
    array[i]   = results[0] > 63.0F ? 63.0F : floor(results[0]);
    array[i+1] = results[1] > 63.0F ? 63.0F : floor(results[1]);
    array[i+2] = results[2] > 63.0F ? 63.0F : floor(results[2]);
    array[i+3] = results[3] > 63.0F ? 63.0F : floor(results[3]);
    // This is slower
//  array[i] = (int) std::min(floor(results[0]), 63.0F);
}

根据我的分析器(Zoom),平方根不需要花费大量时间,但是即使启用了-O2优化,结果的四个裁剪中的每一个都花费了大约20%的时间。有没有更有效的方法来实现循环?请注意,_mm_store_ps() 会通过 gcc 进行优化。

我尝试了平方根的优化表查找,因为 97% 的输入array值低于 512,但这没有帮助。请注意,对于我的完整应用程序(一个持续运行的图像识别应用程序),此例程占用的总处理器时间不到四分之一。

有 MAXPS 和 MINPS:

__m128d _mm_max_ps(__m128d a, __m128d b);

对第一个源操作数和第二个源操作数中的打包单精度浮点值执行 SIMD 比较,并将每对值的最大值返回给目标操作数。

__m128d _mm_min_ps(__m128d a、__m128d b);

对第一个源操作数和第二个源操作数中打包的单精度浮点值执行 SIMD 比较,并将每对值的最小值返回给目标操作数。

使用具有四个 63.0f 值的 XMM 寄存器作为第二个操作数。

鉴于您可以使用非常现代的CPU,我将从以下开始:

float array[SIZE];
for(int i = 0; i < SIZE; i += 8)
{
    __m256 eightFloats, eightRoots;
    eightFloats = _mm256_load_ps(&array[i]);
    eightRoots = _mm256_sqrt_ps(eightFloats);
    float results[8];
    eightRoots = _mm256_floor_ps(eightRoots);
    _mm256_store_ps(results, eightRoots);
    ...
}

如果允许使用最先进的 SIMD 指令,甚至可以使用 512 版本。

总结这两个答案,这是我最终决定为我的全部要求而选择的代码,array[i] = std::min(floor(sqrt(array[i])), (float) 0x3f);

float array[SIZE];
const float clipValue = (float) 0x3f;
const float clipArray[8] = {clipValue, clipValue, clipValue, clipValue,
                            clipValue, clipValue, clipValue, clipValue};
__m256 eightClips = _mm256_load_ps(clipArray);
for(int i = 0; i < SIZE; i += 8)
{
    __m256 eightFloats = _mm256_load_ps(&array[i]);
    __m256 eightRoots  = _mm256_sqrt_ps(eightFloats);
    __m256 eightFloors = _mm256_floor_ps(eightRoots);
    __m256 eightMins   = _mm256_min_ps(eightFloors, eightClips);
    _mm256_store_ps(&array[i], eightMins);
}

我的目标是垂直应用程序中的特定硬件,因此给定了与 AVX 兼容的处理器。