【问题标题】:which mode in intel x86-64 is faster to execute instructionsintel x86-64 中哪种模式执行指令更快
【发布时间】:2018-01-11 04:14:43
【问题描述】:

英特尔有 - 真实模式 - 保护模式 - 虚拟现实模式 - 64位模式

在这些模式中,哪一种执行相同的指令集更快?

使用前缀可以改变类似于其他模式的地址和大小。

【问题讨论】:

  • 我预计 64 位模式会更快,因为众所周知,英特尔会优化最常用的案例(如果你愿意的话,就是“关键路径”)。例如,在 Skylake 上,(i)mul r64 的延迟和 uops 数量比 (i)mul r32 更好(尽管端口 6 压力更大)。然而,虽然一些指令属于特定模式并改变语义,但真正的测试将是执行内存访问。我不期望差异(任何应该在一个时钟周期内涵盖)但可能存在。事实上,实模式(是?)只是保护模式的一种特殊配置,因此应该使用相同的逻辑。
  • 琐事:还有其他模式,如 SMM、(不正确的)虚幻模式、VMX 根/非根、SGX 和(曾经有)ICE 模式。这些模式如何影响性能的问题令人着迷。
  • 场景是,我想写一个通用程序。我想知道哪种模式会更快,为什么?
  • @LakshmanSiddardha:这是操作系统开发人员需要担心的事情。一般程序无法设置自己的首选模式,它们会得到操作系统给它们的任何东西。
  • @BenVoigt:大多数现代 x86-64 操作系统允许用户空间在长模式和兼容模式之间进行选择。

标签: performance x86 x86-64 intel instruction-set


【解决方案1】:

TL:DR:告诉您的编译器生成 64 位可执行文件,以便在大多数情况下获得最大性能。但值得针对 32 位构建进行基准测试,尤其是当您的代码使用大量指针密集型数据结构时。

理论上,更快的 64 位代码几乎总是可行的(一些传统的现实,例如不假设 32 位的 SSE2 和 32 位的传统调用约定,在实践中也支持 64 位),但有时让您的程序在 64 位模式下更快将涉及 ILP32 ABI 之类的东西,例如 Linux x32,或者当您想要至少 32 位的类型时,可能使用 int_least32_t 而不是 long


Intel(和 AMD)CPU 在任何模式下都不会降低解码或执行效率1

但是操作数大小的某些选择比其他选择差(例如,由于partial-register false dependencies or stalls,16 位很烂),并且 16 位代码需要前缀才能使用 32 位操作数大小和地址大小。 Intel CPU 在解码大量前缀时没有问题,但通常较大的代码大小是一件坏事,会降低 L1I 缓存中的代码密度,有时还会降低 uop 缓存中的代码密度。

脚注 1:除非您在 16 位模式下使用 32 位地址大小,例如“big unreal mode”,那么 Intel P6 系列 CPU(即在 Sandybridge 之前)将在每条此类指令上使用 16 位模式下的 32 位 ModRM 寻址模式停止 LCP,即使它实际上并没有改变长度,即 a false LCP stall .地址大小前缀在普通 32 位模式下没有用(除了填充),所以这个问题基本上与 32 位代码无关。


64 位代码具有更大的指令(因为 64 位操作数大小需要 REX 前缀)。通常这无关紧要,因为 uop 缓存和 L1I 缓存通常会完全隐藏代码大小对性能的影响。 32 位和 64 位操作数大小对于大多数指令来说都是相同的速度,并且 64 位代码仍然可以使用 32 位操作数大小,除非它确实需要宽类型,以避免 64 位整数除法的额外成本(以及 REX 前缀)。

场景是,我想写一个通用程序。我想知道哪种模式会更快,为什么?

这与您提出的问题不同。

长模式通常最快,因为完成相同的工作通常需要更少的指令,因为更好的调用约定和更多的寄存器(更少的溢出)。特别是如果您有任何 FP 计算或 SIMD 友好的循环,64 位模式可能是一个巨大的胜利,因为 FP 代码通常可以利用更多的寄存器。

但是 64 位代码中的重指针数据结构的缓存占用量是 32 位代码的两倍(可以在受保护/兼容模式下运行)。此外,具有 64 位对齐要求可能会导致更多的结构填充,因此指针 + int 结构在 64 位代码中将是 16 个字节,而不是 12 个字节。

因此,您可以在 64 位代码中获得更多的缓存未命中,这可能会使其比 32 位代码慢。 Linux's x32 ABI 试图两全其美(对于不需要大量虚拟地址空间的代码):长模式下的 32 位指针。

如果所有“指针”都位于您从中分配的同一个池中,则只需存储 32 位数组索引而不是指针即可。但是beware that it can result in worse load/use latency 因为您(或编译器)需要索引寻址模式,或者单独的添加指令。

在 64 位模式下,JVM(例如)使用一些技巧来“压缩”指针。 https://wiki.openjdk.java.net/display/HotSpot/CompressedOops - 某些类型的指针存储为 32 位,可以左移 3 位以供使用,因为它们指向 8 字节对齐的堆对象。这允许寻址 32GiB 的空间。

【讨论】:

  • 我的印象是,最初典型的 64 位二进制文​​件比使用相同代码的 32 位二进制文​​件几个%。也就是说,规模问题压倒了其他小问题。此外,最初的 64 位芯片在 64 位模式下的指令可能比今天的要慢。我不知道这是否告诉我们很多:从那时起发生了很多变化:编译器在优化 64 位代码方面可能比 32 位更好,一些代码已经改变以减少指针大小的影响,比如 SSE2 是更常用于 64 位等。
  • @BeeOnRope:uop 缓存有助于从需要 REX 前缀的代码大小中减慢解码速度。在此之前,解码/前端瓶颈是一个大问题,较低的代码密度可能是 64 位代码速度较慢的问题的一部分。是的,较旧的 AMD 的 64 位 imul 速度较慢。因此,现代 CPU 无疑已针对 64 位代码进行了很好的优化,即使是到处使用 size_t 的现代编码风格,也不会使用 32 位操作数大小。
  • 不过,现代 CPU 很好地隐藏了 32 位模式的大量成本(例如,通过较少的 reg 和错误的调用约定以及良好的存储转发额外存储/重新加载。AMD Zen 甚至有特殊的零 -当 IIRC 使用相同的简单寻址模式进行存储和重新加载时,延迟存储转发。因此 IIRC 将适用于本地变量的存储/重新加载,尽管可能不会在堆栈上传递参数。)
猜你喜欢
  • 1970-01-01
  • 2011-07-09
  • 1970-01-01
  • 2017-05-30
  • 1970-01-01
  • 2011-05-11
  • 2012-07-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多