【问题标题】:Why are there no NAND, NOR and XNOR instructions in X86?为什么 X86 中没有 NAND、NOR 和 XNOR 指令?
【发布时间】:2021-04-12 05:42:45
【问题描述】:
  • 它们是您可以在计算机上执行的最简单的“指令”之一(它们是我亲自实施的第一批指令)
  • 执行 NOT(AND(x, y)) 会使执行时间和依赖链长度和代码大小加倍
  • BMI1 引入了“andnot”,这是一个有意义的添加,是一个独特的操作 - 为什么不是这个问题标题中的那些?
  • 您通常会在“它们占用宝贵的操作码空间”这些行中阅读答案,但我会查看 AVX512 引入的所有 kmask 操作,顺便说一句,包括 NAND 和 XNOR....... ....................
  • 优化编译器可以生成更好的代码
  • 使用 SIMD 会变得更糟 => 没有 NOT 指令,这需要三倍的执行时间、依赖链长度(编辑:
vpcmpeqd  xmm15, xmm15, xmm15
vpor      xmm0,  xmm0,  xmm1
vpandn    xmm0,  xmm0,  xmm15

【问题讨论】:

  • 顺便说一句,您可以使用vpxor 进行非操作。还有vpternlogd(实现256个逻辑运算的单条指令)呢,它的存在肯定会引起某种争论
  • 你可以(对于所有 3 个),但你仍然需要一个所有位都设置为 1 的掩码 - 或者我错过了什么?吞吐量可能? vpternlogd 只是 AVX512,不是吗?只要 AMD 不实现它,我就不会将它视为真正的指令集 :D 而且它也不适用于 64 位寄存器。
  • 是的,你仍然需要那个全为向量,尽管我认为这并不像你想象的那么糟糕:vpcmpeqd 被认为是独立于它的输入的(这可以追溯到 Core2 。 . 好吧,无论如何,非 VEX 版本,显然当时没有 AVX)并且无论如何它不是 inside 实际 NOT 操作的依赖链,它只是一个侧链
  • 你可以经常安排你的代码不需要反转,例如检查相反的 FLAG 条件。不总是;当然,当您执行一系列按位操作时,它可能会出现。对于像 SPECint 这样的大多数一般工作负载,向 BMI1 添加更多此类指令的真正加速可能非常小。是的,对于 AVX-512 之前的一些 SIMD 版本,比如 AVX2 或 SSE4,它是有意义的,但是由于它们没有,现在添加它们没有什么意义vpternlogd 存在。除非英特尔打算创建 AMD 可能想要实现的新的仅 256 位扩展......
  • 使用vpandn 和归零寄存器有什么技巧?

标签: x86 x86-64 cpu-architecture instructions instruction-set


【解决方案1】:

这些说明不会像您想象的那么有价值,并且一旦创建了基本 ISA,架构师通常不会添加新说明,除非某些重要用例取得了巨大的成功 . (例如,对于大多数代码而言,MMX 总体上并不是一个巨大的胜利,但对于作为早期用例之一的视频/音频编解码器来说却是一个巨大的加速。)

请记住,大多数代码并不是在做无分支的 bithacks。 这只是在 SIMD 中变得更加普遍,在 8086 之后的几十年。我怀疑大多数程序员宁愿使用 nor 而不是 or (8086没有空间用于遵循其正常模式的更多标准 ALU 指令编码1。)许多代码花费大量时间进行比较和分支、循环数据结构(并暂停内存),或做“正常”的数学。当然存在位操作代码,但很多代码并没有涉及太多。

到处保存一两条指令会有所帮助,但前提是您可以使用这些新指令编译整个应用程序。 (虽然大多数 BMI1 和 BMI2 实际上都是这样,例如 SHLX/SHRX 用于 1-uop 复制和移位变量,但英特尔仍然添加它们来修补非常糟糕的 3-uop shift-by-cl。 ) 如果您的目标是特定服务器(这样您可以使用-march=native 构建),那很好,但是很多 x86 代码是提前编译的,用于随机消费机器。像 SSE 这样的扩展可以极大地加速单个循环,因此通常可以分派到单个函数的不同版本以利用这一优势,同时保持较低的基线要求。

但是对于您建议的新添加版本的说明,它不会那样工作,因此添加它们的好处要低得多。而且它们还没有出现,因为 8086 非常狭窄。

但是大多数ISAS没有这些,ARM没有,甚至PowerPC也没有,它选择使用32位指令字中的编码空间来拥有大量的操作码。 (包括像rlwinm这样的整洁的东西,用位范围旋转和掩码,以及其他位域插入/提取到任意位置的东西。)所以这不仅仅是8086传统螺丝x86-64的问题,大多数CPU架构师都是这样不认为值得为这些添加操作码,即使在具有大量空间的 RISC 中也是如此。

虽然MIPS 确实有nor,而不是not。 (MIPS xori 零扩展立即数,因此它不能用于非完整寄存器。)


SIMD 代码:

请注意,一旦您创建了一个全为向量一次,您就可以在循环中重复使用它。大多数 SIMD 代码都在循环中,尽管对单个结构谨慎使用 SIMD 可能会很好。

SIMD 不仅为关键路径增加 1 个周期,为您的 NOR 实现总共增加 2 个周期延迟。在您的示例中,pcmpeqd 不在关键路径上,并且几乎不依赖于所有 CPU 上 reg 的旧值。 (尽管如此,仍然需要一个 SIMD 执行单元来编写它们)。它消耗吞吐量但不消耗延迟。对于给定的代码块,执行时间可能取决于吞吐量或延迟。 (How many CPU cycles are needed for each assembly instruction?(没那么简单)/What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?)

顺便说一句,编译器通常将vpxor 与全1 一起使用,而不是vpandn;唯一的优点是使用内存源操作数,您可以在其中使用 xor 进行 NOT-and-load,这与 vpandn 不同,其中可选内存操作数 (src2) 是未反转的操作数。 dst = ~src1 & src2.


标量代码

您通常可以将代码安排为不需要反转,例如在 OR 之后检查相反的 FLAG 条件。 并非总是如此;当然,当您执行一系列按位操作时,它可能会出现,SIMD 可能更是如此。

向 BMI1 或未来的扩展添加更多此类指令所带来的真正加速可能(已经)对于 SPECint 等大多数一般工作负载来说非常小。

比整数 xnor 等更有价值的可能是像 sub 这样的 common 整数指令的非破坏性 VEX 版本,它不能用 LEA 完成 .所以很多mov/sub 序列可能是vsub。也可能是imulor,可能是and,也可能是shl/shr/sar-立即。但可以肯定的是,如果你要添加东西,还不如拥有 nand、nor 和 xnor。也许标量 abssetcc r/m32 以避免愚蠢的 xor-zeroing 或 movzx 您需要布尔化为 32 位整数。 (当您使用它时,mov r/m32, sign_extended_imm8 如果您可以为其找到一个单字节操作码,例如 64 位模式释放的操作码之一,那么它也将有助于代码密度。)

有一整套糟糕或短视的设计决策,如果能扭转它会很好(或者如果 AVX 修复会很好),例如cvtsi2sd xmm0, eax 合并到 XMM0 中,因此它具有错误的依赖关系,导致 GCC 花费额外的 insn 对目标进行异或归零。 AVX 是一个改变 VEX 版本的行为的机会,并且可能可以通过为现有执行单元提供物理零注册作为合并目标在内部进行处理。 (存在于 SnB 系列的物理寄存器文件中,这就是为什么 xor-zeroing 可以在重命名中完全消除,例如 mov-elimination。)但是不,英特尔尽可能地保留了与传统 SSE 版本一样的所有内容,保留那个短视的 Pentium III 设计决定。 :((PIII 将 xmm regs 拆分为两个 64 位半部分:仅写入低半部分对 SSE1 cvtsi2ss 有利。英特尔继续在 P4 中合并 SSE2 cvtsi2sd 以保持一致性。我猜。)


在 AVX-512 之前的一些 SIMD 版本中添加否定布尔指令可能是有意义的,例如 SSE4.1(它添加了一堆杂项整数的东西,并使事情更加正交,并且只在 45nm Core2 中添加,因此晶体管预算比 MMX 或 SSE1/2 天要高很多),或 AVX(这为 VEX 开辟了很多编码空间) .

但由于他们没有,现在添加它们没有什么意义vpternlogd 存在。除非英特尔打算创建 AMD 可能想要实现的新的传统 SSE 或 256 位 VEX 扩展......

(Legacy-SSE 甚至可以在他们的 Silvermont 系列 CPU 和 Pentium/Celeron CPU 中使用,它们都没有解码 VEX 前缀。这就是为什么不幸的是,即使 Skylake Pentiums 也禁用 BMI1/2 支持以及 AVX1/2/ FMA。这真的很愚蠢,意味着我们无法将 BMI1/2 用作应该在“现代桌面”上运行的提前编译的东西的基线。)


操作码编码空间

VEX 有很多编码空间,掩码指令使用它。此外,AVX-512 仅由高端 CPU 实现;如果英特尔的低功耗 Silvermont 系列 CPU 实现它,那将需要很长时间。因此,需要解码所有这些不同的 VEX 编码掩码指令是 AVX-512 CPU 必须处理的事情。

AVX-512(或前身)最初是为Larrabee 设计的,这是一个变成 Xeon Phi 计算卡的 GPU 项目。因此,AVX-512 ISA 设计选择并不能完全反映您在考虑通用用途的情况下可能设计的内容。尽管拥有大量相对较小的内核意味着您希望避免任何会增加解码器芯片面积或功率过大的事情,所以这并非不合理。

但如果没有 VEX,x86 操作码空间非常拥挤(实际上在 32 位模式下没有留下 1 字节的操作码,剩下的 0f xx 很少。http://ref.x86asm.net/coder32.html)。英特尔(与 AMD 不同)仍然出于某种原因喜欢制造一些无法解码 VEX 前缀的 CPU。当然,他们可以改变这一点并将 VEX 解码添加到 Silvermont,这样他们就可以在不支持 AVX(或所有 BMI2)的情况下使用 VEX 编码的整数指令。 (BMI2 包括 pext/pdep,在专用执行单元中快速实现成本很高。AMD 选择对它们进行微编码,因此它们非常慢,但这让代码可以有效地使用其他 BMI2 指令。)

(不幸的是,CPU 无法(通过 CPUID)宣传它仅支持 128 位矢量大小的 AVX 指令,这将允许更窄的 CPU 仍然获得非破坏性指令。OTOH,没有一些向前兼容代码在支持它的 CPU 上使用更广泛指令的方式,使 128 位 AVX 代码针对当前 CPU 进行优化可能最终被称为“足够好”,并且没有人费心为可以支持的 CPU 制作 256=bit 版本它。)

脚注 1:原始 8086 指令的操作码

对 8086 来说,仅仅解码每个不同的操作码是一项挑战,每个 ALU 指令都有大约 8 种不同的操作码:内存目标、内存源、立即源和特殊情况下的非 modrm AL/AX 形式。每个版本的 8 位和 16 位版本乘以两倍。加上xnor r/m16, sign_extended_imm8。当然,直接形式可以使用 ModRM 中的 /r 字段作为额外的操作码位,但 xnor r/m8, rxnor r, r/m8 和 16 位形式需要 4 个单独的操作码字节,xnor al, imm8xnor ax, imm16 ,所以每条指令有 6 个完整的操作码字节,加上一些重载的操作码 /constant

(半相关:https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code/160739#160739 re:短格式 AL,imm8 编码。)

您可以在原始 8086 操作码中看到的部分模式是,一位在 r/m 目标与 r/m 源之间进行选择,另一位在 8 位和 16 位操作数大小之间进行选择 (Is there a pattern to x86 op codes? (other than direction and size bits) / Are x86 opcodes arbitrary?)。因此,对一些较少见的指令(例如,通过省略 memory-dst 或 8 位形式)采取不同的做法可能会破坏模式,如果是这样,则需要比标准模式更多的晶体管,以便在加载或寄存器获取后馈送 ALU ,或加载/alu/store。

事实上,我认为 8086 没有足够的空间容纳更多支持所有标准形式的 ALU 指令,例如 addor。并且 8086 没有解码任何0f xx 操作码;后来用于扩展。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-12-31
    • 2018-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多