当前位置: 华文星空 > 知识

为什么在本地编译的程序能在不同cpu上运行? 如何区别通用编译和特别的自定义编译?如何进行自定义编译?

2021-03-07知识
如 C 语言在本地编译后已经转换为二进制代码了,而不同 CPU 指令集可能会发生变化,这是不是就会导致在某新 CPU 上编译的软件,在老旧电脑里会因为缺少指令集而无法运行?

Alives 的回答已经提到了编译器的默认选项很保守。我列一下 Linux 发行版的默认选项,可以对保守程度有一个直观的认知:

  • x86-32,在 2010 年前后才提升到 i686 指令集,也就是 1995 年发布的 Pentium Pro 的指令集。
  • 在 2018 年左右,随着各发行版逐步放弃 32 位操作系统,x86-32 只用于 multilib,才提升到 SSE2(因为所有 x86-64 处理器都支持 SSE2),相当于 2000 年发布的 Pentium 4 的指令集。
  • x86-64 和 x86-x32,几乎所有发行版都采用通用指令集,即所有 x86-64 处理器支持的交集,约等于 2003 年发布的初代 Athlon 64 的指令集(除了 3DNow!)。
  • 那么拿到一个软件后应该怎么才能识别出来什么软件是通用编译的,什么软件是自定义编译的?

    如果是想确认能不能正常运行,一般情况下不需要识别。绝大多数软件二进制发布都是用的非常保守的指令集。少部分软件需要充分利用新指令集的优势,通常会编译多个版本的库,主程序启动时根据 cpuid 的结果来动态加载合适的库,新 CPU 可以充分发挥优势,旧 CPU 也能正常运行。至于必须要用新指令集才能运行的软件,都会在发行注记里面写清楚的。

    当然根据 cpuid 动态加载也有可能会因为测试覆盖不全面出问题,尤其是游戏,很难通过虚拟机屏蔽指令集来测试。但这个也没有什么可靠的方法来识别(否则开发者自己识别了就不会出问题了)。比如某著名 MMORPG 最近才出过问题:

    如果是想确认软件能不能发挥新指令集的优势,除了发行注记里面明确提到的,其它软件看看动态链接库的文件名基本上就能确认了。根据 cpuid 动态加载的,都会有类似 libxxx-generic.so libxxx-avx.so libxxx-avx2.so 这样的命名。

    应该如何进行自定义编译?

    GCC/Clang -march 和一大堆 -m 的选项(如 -msse3 -mavx )来控制采用哪些指令集。(注意 GCC 的 x86 包括 x86-32、x86-64、x86-x32。)

    最简单的方法就是直接用 -march=native ,让编译器自己根据 cpuid 开关选项。如果想知道 -march=native 开了哪些选项,执行

    gcc -c -Q -march=native --help=target

    MSVC 的控制没有这么精细,但也有 /arch:SSE /arch:SSE2 /arch:AVX /arch:AVX2 /arch:AVX512 这几个选项。