有效计算 int16 流中的水平对平均值

问题描述 投票:0回答:1

给定一系列一对

int16_t
。每对中的第一项是左声道样本,第二项是右声道样本。我想让它们成为单声道:
mono = (left + right) / 2
并且不想失去哪怕一丁点。 以下程序可以满足我的要求(我很确定):

#include <type_traits>
#include <cstdint>

#include <fmt/format.h>
#include <fmt/ranges.h>

#include <x86intrin.h>

int main()
{
    constexpr auto step = sizeof(__m128i) / sizeof(uint16_t);
    alignas(__m128i) uint16_t input[4 * step];
    uint16_t i = 0;
    for (uint16_t & x : input) {
        x = 1 + 2 * i++;
    }
    alignas(__m256i) uint16_t result[std::extent_v<decltype(input)> / 2];
    for (size_t i = 0; i < std::extent_v<decltype(input)>; i += 4 * step) {
        __m256 vec0 = _mm256_cvtepi16_epi32(_mm_load_si128((const __m128i *)(input + i + 0 * step)));
        __m256 vec1 = _mm256_cvtepi16_epi32(_mm_load_si128((const __m128i *)(input + i + 1 * step)));
        __m256i sum01 = _mm256_hadd_epi32(vec0, vec1);
        __m256i mean01 = _mm256_srai_epi32(_mm256_permute4x64_epi64(sum01, _MM_SHUFFLE(3, 1, 2, 0)), 1);

        __m256 vec2 = _mm256_cvtepi16_epi32(_mm_load_si128((const __m128i *)(input + i + 2 * step)));
        __m256 vec3 = _mm256_cvtepi16_epi32(_mm_load_si128((const __m128i *)(input + i + 3 * step)));
        __m256i sum23 = _mm256_hadd_epi32(vec2, vec3);
        __m256i mean23 = _mm256_srai_epi32(_mm256_permute4x64_epi64(sum23, _MM_SHUFFLE(3, 1, 2, 0)), 1);

        _mm256_store_si256((__m256i *)(result + i / 2), _mm256_permute4x64_epi64(_mm256_packs_epi32(mean01, mean23), _MM_SHUFFLE(3, 1, 2, 0)));
    }
    fmt::println("{}", fmt::join(result, ", "));
}

但是

clang
从主干(对于
-mavx2
)生成的代码似乎被movs过载了:https://godbolt.org/z/cc9v1846n

这是否正常,对性能没有显着影响吗?如果我将其重写为例如,我可以获得多少性能改进?内联汇编与手动寄存器管理?

c++ assembly x86 pcm
1个回答
0
投票

首先,您需要在启用优化的情况下进行编译,否则编译器生成的 asm 完全是一场灾难,尤其是对于需要优化的内置函数的内联包装函数,即使在

之后,其参数和返回值变量也被优化掉了。 force_inline


你可以使用

pmaddwd
(
_mm256_madd_epi16
) 具有
set1_epi16(1)
的常量乘法器,以使用单个 uop 获得水平对的 32 位和,而不是使用 2 个转换和 3-uop
hadd
指令 (2随机播放加上垂直加法)。

这为您提供了版本中的

__m256i sum01
变量(来自一个 256 位负载和
_mm256_madd_epi16(v, _mm256_set1_epi16(1))
,除了按顺序排列的元素,而不是 256 位
hadd
的通道内行为。因此将其打包回去移位后降至 16 位元素不能仅使用
vpackssdw


另一种选择:

pavgw
垂直工作,但您可能可以为其构建 2 个输入,其工作量比扩展和洗牌所需的工作量要少。 但是
_mm256_avg_epu16
适用于无符号16位整数,并且您需要有符号,您可以通过与0x8000进行异或运算(即减去INT16_MIN)将范围转移到无符号,然后对无符号平均值执行相同的操作将其移回。

pavgw
(x + y + 1) >> 1
更像是舍入到最接近的值,而不是除以 2 时的截断。


取决于您需要/想要什么,我不确定

vpmaddwd
vpavgw
中哪一个最终会更有效;诀窍在于优化之前和/或之后的交叉车道洗牌。

© www.soinside.com 2019 - 2024. All rights reserved.