the*_*ilz 1 c++ sse x86-64 simd micro-optimization
ALIGNTO(16) uint8_t noise_frame_flags[16] = { 0 };
// Code detects noise and sets noise_frame_flags omitted
__m128i xmm0 = _mm_load_si128((__m128i*)noise_frame_flags);
bool isNoiseToCancel = _mm_extract_epi64(xmm0, 0) | _mm_extract_epi64(xmm0, 1);
if (isNoiseToCancel)
cancelNoises(audiobuffer, nAudioChannels, audio_samples, noise_frame_flags);
Run Code Online (Sandbox Code Playgroud)
这是我在 Linux 上的 AV Capture 工具的代码片段。这里的noise_frame_flags是16通道音频的标志数组。对于每个通道,相应的字节可以是 0 或 1。1 表示该通道有一些噪声需要消除。例如,如果noise_frame_flags[0] == 1,则意味着设置了第一个通道噪声标志(通过省略的代码)。
即使设置了一个“标志”,我也需要调用cancelNoises
. 这段代码在这方面似乎工作得很好。正如您所看到的,我曾经_mm_load_si128
加载正确对齐的整个标志数组,然后加载两个_mm_extract_epi64
来提取“标志”。我的问题是有更好的方法来做到这一点(也许使用流行计数)?
注意:ALIGNTO(16)
是一个宏扩展以纠正 GCC 等效项,但外观更好。
是的,您最终需要使用 64 位 OR 来查找任一半中的任何非零位,但从uint64_t
128 位加载中获取这些值然后提取的效率并不高。
在 asm 中,您只需要一个mov
负载和一个内存源or
或add
,这将设置 ZF 就像您现在所做的那样。来自同一缓存行的两次加载非常便宜;当前的 CPU 至少具有 2/时钟的负载吞吐量。从单个 128 位负载中提取额外的 ALU 工作是不值得的,即使您对por
单个movq
.
在 C++ 中,使用memcpy
来执行 tmp 变量的严格别名安全加载uint64_t
,然后if(a | b)
. 这仍然是 SIMD,只是SWAR(寄存器内的 SIMD)。
add
甚至比 更好or
:它可以jcc
与Intel Sandybridge 系列(但不是 AMD)上的 大多数指令进行宏融合。or
无法与任何 CPU 上的分支指令融合。由于您的值是0
or 1
,我们不能有两个非零值相加产生零的情况,这就是您通常or
在一般情况下使用的原因。
(某些寻址模式可能会击败英特尔上的微融合或宏融合。或者它可能总是有效,因为没有立即涉及。add rax, [mem]
/确实有可能jnz
作为单个微指令通过前端和 ROB,并在后面执行-end 仅为 2(加载+添加/子分支)。假设它cmp
与我的 Skylake 上的大致相同,除了它确实写入了目的地,因此 Haswell 和更高版本甚至可以在索引寻址模式下保持微融合。 )
uint64_t a, b;
memcpy(&a, noise_frame_flags+0, sizeof(a)); // strict-aliasing-safe loads
memcpy(&b, noise_frame_flags+8, sizeof(b)); // which optimize to MOV qword
bool isNoiseToCancel = a + b; // equivalent to a | b for bool inputs
Run Code Online (Sandbox Code Playgroud)
这应该编译为 3 个 asm 指令,总共解码为 2 uops,或者在 AMD CPU 上解码为 3 条,其中 JCC 只能与cmp
或融合test
。
union { alignas(16) uint8_t flags[16]; uint64_t chunks[2];};
在 C99 中是安全的,但在 ISO C++ 中则不然。大多数(但不是全部)支持 Intel 内在函数的 C++ 编译器定义了联合类型双关的行为。(我认为 @jww 已经说过 SunCC 没有。)
在 C++11 中,您不需要 的自定义宏ALIGNTO(16)
,只需使用alignas(16)
. 如果您也支持 C11#include <stdalign.h>
movdqa
16 字节负载 / SSE4.1 ptest xmm0, xmm0
/ jnz
- Intel CPU 上为 4 uops,AMD 上为 3 uops。
Intelptest
以 2 uops 运行,并且它不能与jcc
.
AMD CPUptest
以 1 uop 运行,但仍然无法熔断。
如果寄存器中有一个全 1 或全 0 常量,ptest xmm0, [mem]
则可以在 Intel 上保存一个 uop(取决于寻址模式),但这仍然是 3 个。
PTEST 仅适用于使用 AVX1 或 AVX2 检查 32 字节数组。(令人惊讶的是,vptest ymm
只需要 AVX1)。然后是关于 AVX2的收支平衡vmovdqa
// vpslld ymm0, 7
/ 。请参阅 TrentP 的答案,了解可移植的 GNU C 本机向量源代码,该代码应该编译为具有可用 AVX 的 x86,并且可能在其他 ISA(例如 ARM)上编译为笨重的东西,具体取决于它们的水平 OR 支持有多好。vpmovmskb eax,ymm0
test+jnz
vptest
popcnt
除非您想根据设置的位数来分解工作,否则不会有用。
在这种情况下,是的,当然,您可以将 bool 数组转换为可以轻松扫描的位图,这可能比_mm_sad_epu8
将归零寄存器求和为两个 8 字节一半更有效。
__m128i vflags = _mm_load_si128((__m128i*)noise_frame_flags);
vflags = _mm_slli_epi32(vflags, 7);
unsigned flagmask = _mm_movemask_epi8(vflags);
if (flagmask) {
unsigned flagcount = __builtin_popcount(flagmask); // popcnt with -march=nehalem or higher
unsigned first_setflag = __builtin_ctz(flagmask); // tzcnt if available, else BSF
vflags &= vflags - 1; // clear lowest set bit. blsr if compiled with -march=haswell or bdver2 or newer.
...
}
Run Code Online (Sandbox Code Playgroud)
(实际上不要使用-march=bdver2
或-march=nehalem
,除非您想设置 ISA 基线,但也想使用-mtune=haswell
或 更现代的东西。有像 和-mpopcnt
之类的单独选项-mbmi
,但通常可以很好地启用某些 CPU 支持的所有 ISA 扩展,因此您不会错过编译器可以使用有用的东西。)
归档时间: |
|
查看次数: |
192 次 |
最近记录: |