Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 避免BZI(y,tzcnt(x))中不必要的mov ecx、ecx指令_C++_Assembly_Visual C++_Bit Manipulation_Compiler Optimization - Fatal编程技术网

C++ 避免BZI(y,tzcnt(x))中不必要的mov ecx、ecx指令

C++ 避免BZI(y,tzcnt(x))中不必要的mov ecx、ecx指令,c++,assembly,visual-c++,bit-manipulation,compiler-optimization,C++,Assembly,Visual C++,Bit Manipulation,Compiler Optimization,我有一个位位置(它永远不会为零),通过使用tzcnt计算,我想从该位置开始将高位归零。 这是C++和拆卸的代码(我使用MSVC): BZI接受无符号int作为第二个参数,但只使用来自rcx的位[7..0],因此我认为这个“mov”指令是不必要的 我用它来计算popcount,所以我也可以用类似的东西,这是一个MSVC遗漏的优化。GCC/clang可以直接在源代码的tzcnt输出上使用bzhi。在某些情况下,所有编译器都缺少优化,但GCC和clang的优化情况往往比MSVC少 (在为Haswell

我有一个位位置(它永远不会为零),通过使用tzcnt计算,我想从该位置开始将高位归零。 这是C++和拆卸的代码(我使用MSVC):

BZI接受无符号int作为第二个参数,但只使用来自rcx的位[7..0],因此我认为这个“mov”指令是不必要的


我用它来计算popcount,所以我也可以用类似的东西,这是一个MSVC遗漏的优化。GCC/clang可以直接在源代码的
tzcnt
输出上使用
bzhi
。在某些情况下,所有编译器都缺少优化,但GCC和clang的优化情况往往比MSVC少

(在为Haswell进行调优时,GCC会小心地中断,以避免通过该错误依赖项创建循环携带的依赖项链的风险。不幸的是,GCC仍然使用
-march=skylake
来实现这一点,它没有为
tzcnt
设置错误的dep,只有
popcnt
和为
bsr/bsf
设置“正确”的dep)


英特尔将
\u bzhi\u u64
的第二个输入记录为
无符号\uuu int32索引
。(出于某种原因,您正在通过对uint32的
静态强制转换
使其显式化,但删除显式强制转换没有帮助)。IDK MSVC如何定义内在函数或在内部处理它

IDK MSVC为什么要这样做;我想知道是不是MSVC的
\u bzhi\u u64
内部逻辑中的64位零扩展,它接受32位C输入,但使用64位asm寄存器。(
tzcnt
的输出值范围为0..64,因此在这种情况下,此零扩展是不可操作的)


屏蔽popcnt:shift
yyy
而不是屏蔽它 如中所述,只需将不需要的位移出,而不是将它们就地归零,效率会更高。(虽然
bzhi
避免了创建掩码的成本,因此这只是盈亏平衡,执行端口
bzhi
shrx
可以运行的模差。)
popcnt
不关心位在哪里

uint64_t popcnt_shift(uint64_t xxx, uint64_t yyy) {
    auto position = _tzcnt_u64(xxx); 
    auto shifted = yyy >> position;
    return _mm_popcnt_u64(shifted);
}

3前端的总UOP=与其他周围代码混合时,总体吞吐量非常好

后端瓶颈:英特尔CPU上端口1(tzcnt和popcnt)有2个UOP。(shrx作为单个uop在端口0或端口6上运行。启用AVX2显然会为MSVC启用BMI2非常重要,否则它将使用3-uop
shr rax,cl
) 关键路径延迟:

  • yyy
    到结果:1表示SHRX,3表示popcnt=4个周期
  • xxx
    到结果:TZCNT为3加上上述=7个循环
不幸的是,GCC对于打破虚假依赖关系过于谨慎,这会导致额外的前端带宽。(但没有额外的后端成本)

无需tzcnt的低延迟备选方案 (但UOP越多,前端吞吐量可能越差。后端执行端口压力的好处取决于周围的代码。)

BMI1有一些bithack指令来做一些事情,比如在Intel上隔离最低设置位、所有1 uop和单周期延迟。(AMD Zen将其作为2个UOP运行,2个周期延迟:)

-获取最高(包括)最低设置位的掩码。您的原始版本不包括
xxx
中的LSB,因此不幸的是,此掩码不能直接使用

uint64_t zmask_blsmsk(uint64_t xxx, uint64_t yyy) {
    auto mask = _blsmsk_u64(xxx); 
    auto masked = yyy & ~(mask<<1);
    return masked;
}

或将隔离最低设置位。
blsi(xxx)-1
将创建一个高达但不包括它的掩码。(对于
xxx=1
,我们将获得

uint64_t zmask2(uint64_t xxx, uint64_t yyy) {
    auto setbit = _blsi_u64(xxx); 
    auto masked = yyy & ~(setbit-1);  // yyy & -setbit
    return masked;
}
MSVC按预期编译,与clang相同:

        blsi    rax, rcx
        dec     rax
        andn    rax, rax, rdx
        ret     0
GCC使用2的补码标识将其转换为该标识,使用可以在任何端口上运行的较短指令。(
andn
只能在Haswell/Skylake上的端口1或端口5上运行)

这是3个UOP(不包括popcnt),但从
xxx
->结果来看,只有3个周期的延迟,低于
tzcnt
/
shrx
(所有这些都不包括3个周期的popcnt延迟),更重要的是,它不会与
popcnt
竞争端口1

(不过,MSVC将其编译为
blsi
+
dec
+
andn
的方式是端口1/端口5的2个UOP。)

最佳选择将取决于周围的代码,吞吐量或延迟是瓶颈。 如果对连续存储的多个不同掩码执行此操作,SIMD可能会很有效。避免使用
tzcnt
意味着您可以使用包含两条指令的位破解来执行最低集隔离或掩码。例如
blsi
(-SRC)bitwiseAND(SRC)
,如英特尔asm手册的操作部分所述。(查找位图表达式的方便位置。)
blsmsk
(SRC-1)XOR(SRC)

SIMD POCPNT可以用 VPUSFB/<代码>在每个字节的两个半部上执行4位并行LUT,并且可以<代码> VpSADBW < /C> >将每个元素水平地累加成计数(以模拟ICE湖的AVX512<代码> VPOCPNTQ)/P> < P>这是编译器的事(如Visual C++ 2019×0435-6000—00000 AA38)。
MSVC的imIntrin.h定义

__int64 _bzhi_u64(unsigned __int64, unsigned int);
遵循与之矛盾的英特尔次优参数(所有
bzhi
param的大小相同)。
叮当声在bmi2intrin.h中的作用

unsigned long long _bzhi_u64(unsigned long long __X, unsigned long long __Y)
因此不需要在代码中触摸
\u tzcnt\u u64
结果


我修补了MSVC的imimintrin.h-但没有用。悲哀!因为Peter复杂的解决方法不适用于我的情况(lzcnt/bzhi,没有popcnt)。

你在做发布版本吗?@user1937198是的,这是在发布中。嗯,gcc和clang都不包括这个mov,所以它看起来确实是MSVC的东西:(您通过对uint32的静态转换来明确这一点…-这只是为了删除可能丢失数据的警告。Blsmsk应该可以做到这一点。我可以稍微更改代码以在位置包含位。因此,不是7个周期,而是至少在英特尔上需要5C。@Marka:Ok perfe
;; MSVC -O2 -arch:AVX2  (to enable BMI for andn)
        blsmsk  rax, rcx
        add     rax, rax               ; left shift
        andn    rax, rax, rdx          ; (~stuff) & yyy
        ret     0
uint64_t zmask2(uint64_t xxx, uint64_t yyy) {
    auto setbit = _blsi_u64(xxx); 
    auto masked = yyy & ~(setbit-1);  // yyy & -setbit
    return masked;
}
        blsi    rax, rcx
        dec     rax
        andn    rax, rax, rdx
        ret     0
;; GCC7.5 -O3 -march=haswell.   Later GCC wastes a `mov` instruction
        blsi    rax, rdi
        neg     rax
        and     rax, rsi
__int64 _bzhi_u64(unsigned __int64, unsigned int);
unsigned long long _bzhi_u64(unsigned long long __X, unsigned long long __Y)