如何在运行时验证架构是否匹配 -march=?

Mat*_*szL 8 c++ x86 gcc cpu-architecture

我们用 编译我们的代码g++ -march=ivybridge -mtune=skylake。如果有人在较旧/不兼容的架构上运行,我希望应用程序能够优雅地通知并退出。我该怎么做呢?AMD处理器怎么样?是否存在某种同等的架构/指令?

eca*_*mur 6

这是令人惊讶的困难,并且是特定于编译器的。我将介绍可用于 gcc 的技术,然后是 clang,然后是替代方案。请记住,截至 2023 年末,所提供的信息是正确的;未来可能会改变。

确定目标处理器

注意:如果您完全控制构建系统,则可以跳过此步骤,因为您可以只传递 eg -march=skylake -DMARCH=skylake,但无论如何它都值得一读,因为稍后将重用某些技术。

gcc (in ix86_target_macros_internal) 设置与处理器对应的预处理器宏;因此,如果您有受支持的处理器列表(例如从 中提取processor_names),您可以测试每个宏(使用#ifdef或在宏内部使用/sf/answers/3479873141/)。示意图:

char const* target_processor() {
    // ...
#if __skylake__
    return "skylake";
#endif
#if __skylake_avx512__
    return "skylake-avx512";
#endif
    // ...
}
Run Code Online (Sandbox Code Playgroud)

请注意,您可能希望确保在没有设置或设置多个宏的情况下发生编译错误,因为在这种情况下您需要更新代码。

确定运行时处理器

gcc 提供__builtin_cpu_is精确的处理器检测。请注意,参数必须是字符串文字,因为在内部它会转换为检查__cpu_modelglobal上的整数字段,该字段是通过cpu_indicator_init调用 from设置的__builtin_cpu_init。因此,您可以再次列出已知的处理器:

char const* runtime_processor() {
    // ...
    if (__builtin_cpu_is("skylake"))
        return "skylake";
    if (__builtin_cpu_is("skylake-avx512"))
        return "skylake-avx512";
    // ...
    return "unknown";
}
Run Code Online (Sandbox Code Playgroud)

请注意,您将需要处理未知处理器的情况,因为您的代码可能在比 gcc 所知的更新的处理器上运行!

确定功能支持

即使您知道目标处理器和运行时处理器,这并不总是意味着您知道该处理器将能够运行您的代码;功能在不同时间添加到服务器和客户端线路,可以删除它们(例如 3dNow),它们取决于供应商(Intel 与 AMD),并且某些功能取决于操作系统支持(例如通过 OSXSAVE 的 AVX,通过 XGETBV 在运行时检测) :gcc 的 __builtin_cpu_supports 是否检查操作系统支持?)。因此,最好-march使用 .NET 来检测运行时是否存在启用的ISA 功能__builtin_cpu_supports

您可以使用processor_features枚举来迭代已知功能,使用设置的宏来ix86_target_macros_internal检测正在使用的功能,并ISA_NAMES_TABLE使用表格在这些功能和功能名称字符串之间进行映射。示意图:

void check_features() {
    // ...
#if __F16C__
    if (!__builtin_cpu_supports("f16c"))
        error("f16c not present");
#endif
#if __RDSEED__
    if (!__builtin_cpu_supports("rdseed"))
        error("rdseed not present");
#endif
    // ...
}
Run Code Online (Sandbox Code Playgroud)

通过构建此代码,您可以确保运行时环境实际上支持您告诉 gcc 可用的所有功能(通过标志-march=)。

不幸的是,对于 clang 来说事情有点棘手。首先,它不设置__processor__宏,因此如果您想通知用户目标处理器,您将需要通过构建系统传递它。

使用X86TargetParser.def检测运行时处理器版本应该可以工作;__builtin_cpu_is与 gcc 上的工作方式大致相同。

其次,clang在支持 gcc 所了解的 ISA 功能方面远远落后- 目前,它没有公开__builtin_cpu_supports对过去任何内容的支持avx512vp2intersect(例如,3dNow、ADX 和更高版本的指令)。您可以直接检查__cpu_model__cpu_features2全局变量,尽管如果您使用 ,这将不起作用-rtlib=compiler-rt,因为编译器-rt 只设置一组有限且过时的标志。

希望这个问题很快就能得到解决,但与此同时,您可能需要自己编写检查并参考 cpuid 标志;请get_available_features参阅 libgcc 如何执行此操作,以及getAvailableFeatures相应的编译器 rt 代码。

一种勉强可行的技术可能是编译具有不同-march=标志的单独 TU,并使用它来静态检测 clang 期望每个处理器可用的 ISA 功能;但是,这不适用于有条件存在的功能或您的 clang 版本不知道的较新处理器。

微架构级别

更好的替代方案可能是将您的目标架构设置为 x86-64 psABI定义的四个微架构级别(x86-64-v1 到 x86-64-v4)之一,即在您的情况下(v3 是 Haswell,v4 是 Skylake) -AVX512 / Cannon Lake),并使用ifunc 多版本支持 ( ) 为性能关键型代码提供中间和专用处理器支持。-march=x86-64-v2-mtune__attribute__ ((target)) / [[gnu::target]]

这种方法的优点是您可以编写__builtin_cpu_supports("x86-64-v2")(自 gcc 12 起)并知道您至少有一个支持该微体系结构级别的处理器。缺点是 clang 尚未发布对此的支持,但它将在 clang 18 中发布

混合架构

如果您在具有基于不同架构构建的性能和效率核心的处理器(例如 Alder Lake、Raptor Lake)上运行,则可能会担心这将如何工作,因为它们的芯片支持不同的指令集。如果硬件公开不同的功能集,如果您的进程可能在 P 核上启动,然后在 E 核上重新调度(或调度一些线程),则可能会出现问题,这就是 CPU 不这样的原因。当前的软件还没有为此做好准备(并且没有计划对此进行更改)。

Alder Lake 在 P 核心上禁用了 AVX-512,并在 E 核心上添加了 AVX2+BMI2 支持,将所有核心带到 x86-64-v3 以及除 AVX-512 之外的各种其他扩展。 效率核心是否支持与性能核心相同的指令?(是的)。由于性能特征不同,内核之间的适当调整选择仍然可能有所不同。(请参阅Agner Fog 的博客。)

gcc -mtune=alderlake(我认为)适用于 P 核心。 -mtune=gracemont适用于 E 核。(或者对于只有 E 核的 CPU。)作为-march设置,它们启用相同的扩展集。

早期的 Alder Lake 系统允许在 BIOS 中禁用 E 核心或根本不存在 E 核心的情况下启用 AVX-512,但不幸的是英特尔改变了主意,新的微代码不允许 AVX-512(某些 BIOS供应商已经解决了),并且较新的 CPU 步进物理上熔断了 AVX-512,因此即使是旧的微码版本也无法启用它。

Agner Fog 关于如何检测 Intel Alder Lake CPU 中的 P/E-Core?表示 P 和 E 核心通过报告相同的系列/型号cpuid(除了启用 AVX-512 的旧 Alder Lake 上)。但其他一些cpuid叶子有不同的数据,根据英特尔混合 CPU 文档,有一个叶子用于检测核心类型。

(除非这种差异因与不良 DRM 的兼容性而被禁用,该 DRM会检测 P 和 E 核心,因为不同的系统试图在同一键上玩游戏。某些 BIOS 中有一个遗留游戏兼容性功能;它可能通过干扰检测来工作机制。)