为什么X86中没有NAND、NOR和XNOR指令?

MrU*_*e92 3 x86 x86-64 instruction-set cpu-architecture instructions

  • 它们是您可以在计算机上执行的最简单的“指令”之一(它们是我亲自实施的第一个指令)
  • 执行 NOT(AND(x, y)) 会使执行时间 AND 依赖链长度 AND 代码大小加倍
  • BMI1 引入了“andnot”,这是一个有意义的补充,是一个独特的操作 - 为什么不是这个问题标题中的那些?
  • 您通常会在“它们占用宝贵的操作码空间”行中阅读答案,但随后我会查看 AVX512 引入的所有 kmask 操作,顺便说一句,其中包括 NAND 和 XNOR...... ............
  • 优化编译器可以生成更好的代码
  • SIMD 的情况会变得更糟 => 没有 NOT 指令,这需要三倍的执行时间、依赖链长度(编辑:<= not true;感谢@Peter Cordes)和代码大小,而不是加倍:
vpcmpeqd  xmm15, xmm15, xmm15
vpor      xmm0,  xmm0,  xmm1
vpandn    xmm0,  xmm0,  xmm15
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 11

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

请记住,大多数代码都不会进行无分支黑客攻击。nor在 8086 几十年后,这种情况在 SIMD 中变得更加常见。 我怀疑大多数程序员都不愿意or(8086 没有空间留给遵循其正常模式的更标准的 ALU 指令编码1。)很多代码花费了大量的时间时间比较和分支,循环数据结构(并停止内存),或做“正常”数学。当然,位操作代码是存在的,但很多代码并不涉及太多。

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

但对于您建议的新添加的说明版本来说,这种方式就行不通了,因此添加它们的好处要低得多。他们还没有在场,因为 8086 太拥挤了。

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

尽管MIPS 确实有nor, 而不是not. (MIPSxori对立即数进行零扩展,因此它不能用于非完整寄存器。)


SIMD代码:

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

SIMD 不仅在关键路径上增加了 1 个周期,而且为您的 NOR 实现带来了总共 2 个周期的延迟。在您的示例中,pcmpeqd它脱离了关键路径,并且不依赖于几乎所有 CPU 上的 reg 的旧值。(不过仍然需要一个 SIMD 执行单元来编写这些单元)。它会消耗吞吐量,但不会消耗延迟。对于给定的代码块,执行时间可能取决于吞吐量或延迟。(每个汇编指令需要多少个 CPU 周期?(没那么简单)/预测现代超标量处理器上的操作延迟需要考虑哪些因素以及如何手动计算它们?

顺便说一句,编译器经常使用vpxor全一而不是vpandn; 唯一的优点是使用内存源操作数,您不能使用异或进行加载,这与vpandn可选内存操作数 (src2) 是不反转的操作数不同。 dst = ~src1 & src2


标量代码

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

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

比整数xnor等更有价值的可能是常见整数指令的非破坏性 VEX 版本,就像subLEA 无法完成的那样。所以可能有很多mov/序列。也可能是,,也许,也许// -立即。但可以肯定的是,如果您要添加东西,不妨使用 nand、nor 和 xnor。也许是标量,为了避免愚蠢的-zeroing,或者您需要布尔化为 32 位整数。(当您这样做时,如果您能为它找到一个单字节操作码,例如 64 位模式释放的操作码之一,也将有利于代码密度。)subvsubimulorandshlshrsarabssetcc r/m32xormovzxmov r/m32, sign_extended_imm8

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


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

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

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


操作码编码空间

VEX 有大量的编码空间,掩码指令使用它。另外,AVX-512仅由高端CPU实现;英特尔的低功耗 Silvermont 系列 CPU 实现这一功能还需要很长一段时间。因此,需要解码所有不同的 VEX 编码掩码指令是 AVX-512 CPU 必须处理的问题。

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

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

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

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

对于 8086 来说,解码每个不同的操作码都是一个挑战,每个 ALU 指令大约有 8 个不同的操作码:内存目标、内存源、立即源和特殊情况的 no modrm AL/AX 形式。对于每个版本的 8 位和 16 位版本,乘以 2。再加上xnor r/m16, sign_extended_imm8。当然,立即数形式可以使用/rModRM 中的字段作为额外的操作码位,但是xnor r/m8, rxnor r, r/m8和 16 位形式将需要 4 个单独的操作码字节,并且xnor al, imm8和也将需要xnor ax, imm16,因此每条指令需要 6 个完整的操作码字节,加上一些重载的操作码/持续的

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

您可以在原始 8086 操作码中看到的部分模式是,一位在r/m目标与r/m源之间进行选择,另一位在 8 和 16 位操作数大小之间进行选择(是否有 x86 操作码的模式?(除了方向之外)和大小位) / x86 操作码是任意的吗?)。因此,对一些较罕见的指令采取不同的做法(例如,通过省略内存 dst 或 8 位形式)可能会破坏该模式,如果是这样,则需要比标准模式更多的额外晶体管,以便在加载或寄存器获取后为 ALU 供电,或加载/铝/存储。

事实上,我认为 8086 没有为再一条支持所有标准形式(例如addor )的 ALU 指令留下足够的空间or。8086 没有解码任何0f xx操作码;这是后来的扩展。