给定一系列一对
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
这是否正常,对性能没有显着影响吗?如果我将其重写为例如,我可以获得多少性能改进?内联汇编与手动寄存器管理?
首先,您需要在启用优化的情况下进行编译,否则编译器生成的 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
中哪一个最终会更有效;诀窍在于优化之前和/或之后的交叉车道洗牌。