Optimization 收集16位整数的AVX2&512内部值?

Optimization 收集16位整数的AVX2&512内部值?,optimization,avx2,avx512,Optimization,Avx2,Avx512,想象一下这段代码: void Function(int16 *src, int *indices, float *dst, int cnt, float mul) { for (int i=0; i<cnt; i++) dst[i] = float(src[indices[i]]) * mul; }; 这确实需要收集内部信息,例如_mm_i32gather_epi32。我在加载浮点数时获得了巨大的成功,但是有16位整数的浮点数吗?这里的另一个问题是,我需要从输入上的16位转换为输

想象一下这段代码:

void Function(int16 *src, int *indices, float *dst, int cnt, float mul)
{
    for (int i=0; i<cnt; i++) dst[i] = float(src[indices[i]]) * mul;
};

这确实需要收集内部信息,例如_mm_i32gather_epi32。我在加载浮点数时获得了巨大的成功,但是有16位整数的浮点数吗?这里的另一个问题是,我需要从输入上的16位转换为输出上的32位浮点。

确实没有收集16位整数的指令,但是假设没有内存访问冲突的风险,您可以从相应的地址开始加载32位整数,并屏蔽每个值的上半部分。 对于uint16,这将是一个简单的位,对于有符号整数,可以将值向左移动,以使符号位位于最高有效位置。然后,您可以在将值转换为浮点值之前对其进行算术上的向后移位,或者,因为您还是要将它们相乘,所以只需相应地缩放乘法因子。 或者,您可以先从两个字节开始加载,然后以算术方式向右移位。无论哪种方式,您的瓶颈都可能是装载端口vpgatherdd需要8个装载UOP。加上索引的负载,您有9个负载分布在两个端口上,这将导致8个元素的4.5个周期

未经测试的可能AVX2实现不会处理最后的元素,如果cnt不是8的倍数,只需在末尾执行原始循环:

void Function(int16_t const *src, int const *indices, float *dst, size_t cnt, float mul_)
{
    __m256 mul = _mm256_set1_ps(mul_*float(1.0f/0x10000));
    for (size_t i=0; i+8<=cnt; i+=8){ // todo handle last elements
        // load indicies:
        __m256i idx = _mm256_loadu_si256(reinterpret_cast<__m256i const*>(indices + i));
        // load 16bit integers in the lower halves + garbage in the upper halves:
        __m256i values = _mm256_i32gather_epi32(reinterpret_cast<int const*>(src), idx, 2);
        // shift each value to upper half (removes garbage, makes sure sign is at the right place)
        // values are too large by a factor of 0x10000
        values = _mm256_slli_epi32(values, 16);
        // convert to float, scale and multiply:
        __m256 fvalues = _mm256_mul_ps(_mm256_cvtepi32_ps(values), mul);
        // store result
        _mm256_storeu_ps(dst, fvalues);
    } 
}

将其移植到AVX-512应该是直截了当的。

Skylake上的AVX2 vpgatherdd ymm只需要4 uops 5c吞吐量,显然每个uops要进行多个缓存访问。因此,瓶颈不是执行端口或前端,与vpinrsw手动收集相比,它可以更好地与周围的工作重叠。但它确实会在缓存读取端口或类似端口上出现瓶颈,吞吐量略低于2次缓存读取/时钟。SKX上的AVX512版本对于ZMM来说是9c,因此与16次缓存访问所希望的8次相比,又多了1次循环。对于未对齐的DWORD,偶尔的缓存线拆分可能会使情况变得更糟。将移位校正比例因子烘焙到OP希望使用的浮点mul常量中是个不错的主意。感谢@PeterCordes的评论。我想我误解了+8*p23 in-或者它只是一个术语,即加载在技术上不是µ-op?页面上确实说Skylake或更高版本只需要5个UOP,而不是11个。哦,我只是在看Agner的指南,他没有完全分解后端端口的使用情况,只需要哪些端口和前端UOP的总数。是的,8*p23确实为每个缓存访问使用后端加载端口uop。这更有道理。但不知何故,它设法在后端执行这些UOP,而不通过前端发送那么多。那很酷。我想知道这是否能让他们从后端的RS上重播或者其他什么;用于处理缓存未命中和缓存线拆分。没有足够的前端UOP用于micro fusion来解释它。但是无论如何,是的,你仍然可以通过前端获得其他ALU UOP,从而使ALU工作与后端的聚集并行进行。加上不需要任何洗牌来组合dword块,这使得聚集在某些情况下非常有用。