gcc - 几年前构建的gcc版本的编译器如何仍能针对最近发布的处理器进行编译?

标签 gcc optimization compiler-construction intel compiler-optimization

假设我使用一个编译器:gcc 4.8。而来自英特尔的处理器,比如说skylake或其他一些花哨的新家庭。

检查以下问题:How to see which flags -march=native will activate?;如果我执行gcc -march=native -E -v - </dev/null 2>&1 | grep cc1,这将为主机(上面的处理器skylake)喷出一些标志。

gcc如何知道要启用哪些标记禁用...何时在Skylake处理器出现之前发布4.8?那其他较新的处理器系列又如何呢?

因此,下一个问题是将编译器升级到最新版本,以便针对目标处理器准确,最佳地编译它,这是新的吗?

这个问题并不是真正针对gcc / intel,我想知道其他人也如何保持处理器和编译器之间的同步。

最佳答案

旧的编译器不知道如何调整新的微体系结构。 (而且通常也缺少更好的优化功能:新版本的gcc / clang通常会添加新的优化功能,全面帮助您,例如,gcc8可以将多个相邻的小变量或数组元素的加载/存储合并为单个4或8字节加载或存储。这对一切都有帮助。)

他们也只能使用他们知道的ISA扩展。

它们可以编写正确的代码,因为新的x86 CPU仍然是x86,并且与旧CPU1的代码向后兼容。与ARM相同。 ARMv8 ISA向后兼容ARMv7,ARMv6等,因此新的ARM CPU可以运行现有的ARM二进制文件。 (有些AArch64 CPU放弃了对32位模式的支持,但请不要介意。)


因此,下一个问题是将编译器升级到最新版本,以便针对目标处理器准确,最佳地编译它,这是新的吗?


是的,您希望编译器至少了解有关CPU的调整选项。

但是可以,即使您的CPU不是新的,也总是这样。新的编译器版本也常常使旧的CPU受益,但是可以使用一组新的SIMD扩展来自动矢量化,这可能会导致潜在的大幅加速,从而在一个热循环中花费大量时间。假设该循环很好地自动矢量化。

例如Phoronix最近发布了GCC 5 Through GCC 10 Compiler Benchmarks - Five Years Worth Of C/C++ Compiler Performance,他们在i7 5960X(Haswell-E)CPU上进行了基准测试。我认为GCC5了解-march=haswell。在某些基准测试中,GCC9.2所编写的代码甚至比gcc8快得多。

但是我可以保证这不是最佳的!编译器在大型规模上是好的,但是如果他们知道针对给定微体系结构进行优化的低级细节,他们通常可以在一个热循环中找到一些东西。它与从任何编译器中获得的效果一样好。 (实际上存在性能回归,因此即使并非总是如此。如果找到错误,请提交未优化的错误)。



-march=native做两件事


CPU功能检测以启用-mfma-mbmi2之类的东西。使用CPUID instruction在x86上这很容易。 GCC将启用它所知道的所有扩展,这些扩展受实际CPU支持。例如我认为GCC4.8是第一个了解所有AVX512扩展的GCC,因此您甚至可以在Ice Lake或Skylake-avx512上获得一些AVX512自动矢量化功能。对于任何不平凡的事情,它是否做好都是另一回事。但是没有带有GCC4.7的AVX512。
CPU类型检测设置-mtune=skylake。这取决于GCC实际上将您的特定CPU识别为它所知道的东西。如果不是,则退回到-mtune=generic。它可能会检测(使用CPUID)您的L1 / L2 / L3缓存大小,并使用它来影响某些调整决策(例如内联/展开),而不是将已知大小用于-mtune=haswell。我认为这没什么大不了的。当前的编译器并不AFAIK引入缓存块优化来使循环或类似的事情发生,而这正是了解缓存大小的真正所在。


CPU类型检测也可以在x86上使用CPUID。供应商字符串和型号/系列/步骤编号唯一标识微体系结构。 ((wikipedia)sandpileInstLatx64https://agner.org/optimize/

x86旨在支持在多个微体系结构上运行的单个二进制文件,并且可能希望对运行时功能进行检测/调度。因此,一种有效/可移植/可扩展的CPU检测机制以CPUID指令的形式存在,已在Pentium和某些后来的486 CPU中引入。 (因此是x86-64的基准。)

其他ISA通常用于嵌入式应用中,在该嵌入式应用中针对特定CPU重新编译代码。他们大多对运行时检测没有很好的支持。 GCC可能必须为SIGILL安装处理程序,然后尝试运行一些指令。或查询知道支持什么的操作系统,例如Linux的/proc/cpuinfo



脚注1:

特别是对于x86,其成名/流行原因的主要原因是严格的向后兼容性。一个无法运行某些现有程序的新CPU将很难销售,因此供应商不会这样做。他们甚至会向后弯腰以超越纸上的ISA文档,以确保现有代码能够正常工作。正如前英特尔架构师安迪·格莱夫(Andy Glew)所说:All or almost all modern Intel processors are stricter than the manual.(用于自修改代码,一般而言)。

当您以旧版BIOS模式启动时,以及为磁盘,键盘和屏幕访问实现软件ABI时,现代PC主板固件甚至仍可以模拟IBM PC / XT的旧版硬件。因此,即使引导加载程序和GRUB之类的东西也具有一致的向后兼容接口,可以在加载内核之前使用,该内核具有实际存在的实际硬件的驱动程序。

我认为现代PC仍可以16位实模式运行真实的MS-DOS(操作系统)二进制文件。

在不破坏向后兼容性的情况下添加新的指令操作码会使变长的x86机器代码指令变得更加复杂,并且x86历史上的粗心/反竞争性发展也无济于事,例如,导致SSSE3和更高版本的指令编码更加肿。请参阅Agner Fog的文章Stop the instruction set war

但是,依赖rep foo解码为foo的代码可能会中断:英特尔手册非常清楚,随机前缀将来可能会导致代码行为异常。这样一来,对于Intel或AMD来说,就可以安全地引入新指令,这些指令可以在旧CPU上以已知方式进行解码,而在新CPU上可以执行新的操作。如pause = rep nop。或事务性内存HLE在lock指令上使用旧CPU将忽略的前缀。

小心选择VEX(AVX)和EVEX(AVX512)之类的前缀,以免与有效的指令编码重叠,尤其是在32位模式下。请参见How does the instruction decoder differentiate between EVEX prefix and BOUND opcode in 32-bit mode?。这就是为什么即使使用VEX或EVEX,32位模式仍只能使用8个向量寄存器(zmm0..7)的原因之一,在64位模式下,它们分别允许ymm0..15或zmm0..31。 (在32位模式下,VEX前缀是某些操作码的无效编码。在64位模式下,该操作码首先对后面的字节无效,这样更灵活。但是为简化解码器硬件,它们不是根本不同。)

2014年的MIPS32r6 / MIPS64r6是一个不向后兼容的著名示例。它为保持不变的指令重新排列了一些操作码,并删除了一些指令以将其操作码重新用于其他新指令,例如分支没有延迟槽。这是非常不寻常的,并且仅对用于嵌入式系统(例如当前的MIPS)的CPU有意义。对于嵌入式系统,重新编译MIPS32r6的所有内容都不是问题。



某些编译器可以使二进制文件进行运行时CPU检测和调度,因此它们可以利用CPU所支持的任何优势,但是当然仍然仅适用于编译器在编译时知道的扩展。函数的AVX + FMA机器代码版本必须存在于可执行文件中,因此,甚至在宣布这些功能之前的编译器就无法创建此类机器代码。

并且在具有这些功能的实际CPU可用之前,编译器开发人员还没有机会为这些功能调整代码源,因此,较新的编译器可能会为相同的CPU功能编写更好的代码。

GCC通过its ifunc mechanism为此提供了一些支持,但是IIRC如果没有源代码更改,您将无法做到这一点。

我认为,英特尔的编译器(ICC)在自动向量化时仅通过命令行选项就支持多版本的某些热门功能。

关于gcc - 几年前构建的gcc版本的编译器如何仍能针对最近发布的处理器进行编译?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59560902/

相关文章:

execvpe 函数的编译器警告

c - header 中定义了 var 的重复符号和多重包含保护

optimization - Hive连接查询优化

c - 引用编译器设计

linux - Glibc 编译需要多长时间?

c++ - 为什么定义没有返回类型的 main() 编译没有错误?

编译 c 源代码时找不到 -lagent(不兼容的库)

python - SciPy 曲线拟合失败幂律

c++ - 使用 LLVM-gcc 或 gcc 进行部分评估/特化

java - 使用 javax.tool 进行级联内存编译