我正在寻找GCC/CLANG的编译器标志来生成BEXTR
指令。
template <auto uSTART, auto uLENGTH, typename Tunsigned>
constexpr Tunsigned bit_extract(Tunsigned uInput)
{
return (uInput >> uSTART) & ~(~Tunsigned(0) << uLENGTH);
}
template unsigned bit_extract<2, 3>(unsigned);
字符串
编译器标志-std=c++17 -O2 -mbmi
* 不 * 生成BEXTR指令。(我在编译器资源管理器中的示例)
编译器标志-std=c++17 -O2 -march=native
* 可能 * 生成BEXTR指令。(我在编译器资源管理器中的示例)
我应该使用什么编译器标志来生成BEXTR
指令?
1条答案
按热度按时间f2uvfpb91#
clang
-mbmi
和-march=native
之间的相关差异为**-mtune=znver3
**。Godbolt运行在AWS示例上,这些示例可以是Zen 3、Ice Lake,有时也可以是更早的Intel示例,如SKX或Cascade Lake。
bextr
是AMD Zen系列上的单uop(具有1个周期延迟),因此有时值得与mov-immediate一起使用以设置控制常数。从数据输入到数据输出的关键路径延迟仅为1个周期。https://uops.info/显然,clang知道这一点,当它对
-march=znver*
有用时,它会使用BEXTR(这意味着-mtune),但在-march=gracemont
也是1 uop(桤木Lake P内核和一些即将推出的低功耗芯片)的情况下,它不会使用。GCC可能根本不寻找BEXTR模式,至少不使用常量移位计数。使用您的表达式作为运行时变量start,length,它使用BMI 2 SHRX+BZHI,或者更糟糕的序列
-mno-bmi2
。在英特尔P核上,BEXTR为2 uops(和2c潜伏期),因此不优于即时SHR和AND。(或者GCC用于大于32的位范围的
shl
/shr
,其中掩码不适合AND的立即数。)mov
寄存器副本可以从移动消除中受益,或者更聪明的编译器可以在屏蔽之前使用rorx
进行复制和移位,以优化Ice Lake,其中对于整数n,禁用了mov消除。对于32位操作数大小,rorx
将比mov+shr花费更多的代码大小,但对于64位来说,这是收支平衡的,其中两个指令都需要雷克斯。BEXTR的2个uop分别在端口0/6和端口1/5上运行。除了在桤木Lake P核上,第二个uop只能在端口1上运行。它可能与BMI 2
shrx
(p0/p6)和BMI 2bzhi
的内部uop相同,其中桤木Lake进行了相同的更改1:bzhi
仅为端口1,而在Haswell之后的早期Intel中为p1/5。BMI 1是2012年AMD的Piledriver.(https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set)的新功能。当英特尔决定支持它时,他们决定在移位和新的
bzhi
操作方面实现bextr
,他们直接将其公开为BMI 2指令。对于英特尔来说,BMI 1和BMI 2在Haswell中都是新功能。AMD的TBM(trailing bit manipulation)包括
bextr
的立即版本,具有16位立即BMI 1
bextr
的理想用例是一个循环不变但运行时可变的位范围。打包控制字段可能比在shrx
的寄存器中生成移位计数和and
或andn
的掩码更便宜,而且只有一个寄存器专用于保存这些。Clang针对具有运行时可变位字段位置的非循环单一用例执行此操作,但具有讽刺意味的是,不针对循环执行此操作。
字符串
Godbolt编译器输出:
的数据
在一个循环中,
bextr
的完美用例,clang避免了它。/facepalm。GCC也是如此,但是我们刚刚看到clang使用bextr
for one 提取,而不是在一个具有相同调优选项的循环中。的字符串
循环体可能是
bextr rax, [rsi + rcx*8], rdx
/mov [rdi + rcx*8]
+inc ecx
或其他什么(显然bextr
不能微融合内存源操作数,所以它总是一个额外的uop,即使在AMD和even with a one-register addressing mode on Intel上也是如此)。Footnote 1:桤木Lake上的https://uops.info/错误延迟测量
**
bextr
是桤木Lake上的两个单周期uop,与早期的Intel相同。shrx
和bzhi
仍然是单周期延迟,而不是3。**考虑到另一个得到这个正常结果的测量,以及uops.info测量中的不一致性,以及Intel CPU架构师的假定正常性,几乎可以肯定这是一个微基准测试错误。uops.info 在
shrx
、bzhi
和bextr
中测量了每uop有3个周期延迟的桤木Lake P内核,但从CPU设计PoV来看,这将是疯狂的(特别是shrx
/shlx
,而shl reg, cl
对于reg结果仍然是1个周期延迟!uops.info测量了一个输入的4c延迟,而另一个输入的6c延迟,但英特尔一般不支持延迟转发(其中,UOP可以在仅准备好其一些输入的情况下开始)。如果一个UOP仅预处理不在关键路径上的输入,则3C可能是合理的,但4C不是。他们还测量了内存源
shrx
从移位计数到结果的1c延迟,这与reg-source为3个周期不一致,但与1c延迟移位一致。InstLatx64测量i9- 12900 K桤木Lake的Golden Cove P-cores时,
bzhi
和shrx
具有单周期延迟,bextr
具有2周期延迟,使用简单的测量序列,仅重复指令。InstLat还确认了吞吐量变化(对于bzhi
和bextr
为1/时钟)。这些都是有意义的,符合我的预期。所以我认为桤木Lake上的指令序列uops.info用来测量延迟的东西一定很奇怪。他们使用
movsxd
在测试中的指令之间建立依赖链(以区分多uop指令的不同输入,以及在像shl reg,cl
这样的东西的情况下,通过FLAGS基本上错误的依赖关系的延迟链比数据移位本身慢。)uops.info 显示了每个实验的实际循环体,以及原始的perf计数器数字,这太棒了,比任何其他指令表都要好。它允许更深入地挖掘,看看在令人惊讶的情况下实际测量了什么,在以前的一些情况下,这足以看到发生了什么,并向Andreas Abel报告错误(他已经修复了以前情况下受影响指令的测试顺序。)我给他发了邮件,一旦我们弄清楚发生了什么,他会更新的。
桤木Lake seem normal上的
movsxd r64, r32
测量,3个端口(1/5/B)中任何一个的1c延迟,先测量MOVSXD R9, R8D
,再测量movsxd r8,r9d
,然后是一个4 xmovsxd r8,r8d
链。(InstLatx 64报告
MOVSXD r1_64, r2_32
有0.3c的延迟,但实际上应该报告为“没有真正的dep”,就像cmp
一样,因为他们没有创建一个将结果反馈到输入的dep链。不像他们的XCHG r1_32, r2_32
,单独的数据库让我们看到1.5c的平均延迟,因为它是2c的一种方式,所有这一切告诉我们没有(假)输出依赖,就像lz/tzcnt
和popcnt
在Intel上有一段时间了。桤木Lake还没有开始为movsx[d]做mov-elimination和为movzx做16位源代码。)测量的数字对于他们实际测试的序列是准确的,这些是延迟测试,所以前端效应可能不是问题。例如for
bextr r32,r32,r32
on ADL。对于测试“Latency operand 2 → 1:6 cycles",循环体是型
他们预计5条
movsxd
指令的“链延迟”为5个周期。他们测量了11.0
核心时钟周期和UOPS_EXECUTED.THREAD: 7.0
,这排除了movsxd
的任何移动消除(这本来是令人惊讶的。)来自bextr
的2个uop,加上5movsxd
。(movsxd
的任何较低延迟都会使bextr
的计算延迟更高,因为计算假设每个都是1c。)我不知道
movsxd
有什么特别之处,但用lea r8d, [r9+1]
测试也可以作为具有1个输入和一个只写目的地的1c延迟指令工作。(在早期的CPU上,莱亚在较少的端口上运行,因此movsxd
是一个不错的选择。)在桤木Lake的Golden Cove P内核上可以消除具有64位目的地和小型
[reg+disp8]
寻址模式的lea
,零延迟,但不适用于32位目的地。(尽管如此,这可能会改变,所以lea
实际上不是一个很好的未来证明的选择)。IDK他们如何写一个不同的寄存器值没有一个ALU uop;也许RAT(寄存器分配表)支持某种偏移量,可以在以后的寄存器读取过程中添加?像堆栈引擎一样,但可以由后端uops读取?它也适用于add r64, imm8
,但不适用于add r32, imm8
。https://www.anandtech.com/show/17047/the-intel-12th-gen-core-i912900k-review-hybrid-performance-brings-hybrid-complexity/5在Gracemont E-cores上,anandtech的文章说
movsx
应该有移动消除,但是uops.info测量了所有reg-source情况下的1c延迟。LEA_B_IS_D8 (R32)
例如在2c延迟下测量的lea r8d,[r14+r13*2+0x8]
可能是这种效应的一个例子。Anandtech更新日志说“莱亚与规模延迟2->3时钟”为金湾与塞浦路斯湾,所以测量实际上是一个周期快于它显然应该。其他受影响的指令包括简单整数指令的FLAG结果,如
add
、sub
和and
,以及andn
,其中uops.info将其延迟报告为[1:2],这意味着对于某些输入和输出的组合(在本例中,输入到输出reg)为1c,对于其他组合(输入到FLAGS)为2c。因此,奇怪的不仅仅是具有单独的只写目的地的指令。(但FLAGS在技术上是一个单独的输出,而不是输入之一。)