(已编辑)何时应在 c 中使用内联程序集(优化之外)



注意: 经过编辑以使问题不基于oppion

假设

  1. 我们处于用户模式(不在内核中)
  2. 正在使用的操作系统是Linux的现代版本或使用x86 CPU的现代版本的Windows。

除了优化之外,是否有需要在 C 程序中使用内联程序集的特定示例。(如果适用,请提供内联程序集)

需要明确的是,通过使用关键字注入汇编语言代码__asm__(在GCC的情况下)或__asm

(在VC++的情况下)

(其中大部分是为问题的原始版本编写的。 之后进行了编辑)。
您的意思是纯粹出于性能原因,因此排除在操作系统内核中使用特殊指令?

你最终真正想要的是高效执行的机器代码。 并且能够修改一些文本文件并重新编译以获得不同的机器代码。 您通常可以在不需要内联 asm 的情况下获得这两件事,因此:

https://gcc.gnu.org/wiki/DontUseInlineAsm

GNU C 内联汇编很难正确使用,但如果你正确使用它,它的开销非常低。 尽管如此,它仍然阻止了许多重要的优化,如常量传播。

有关如何有效/安全地使用它的指南,请参阅 https://stackoverflow.com/tags/inline-assembly/info。 (例如,使用约束而不是愚蠢的mov指令作为ASM模板中的第一个或最后一个指令。


几乎总是不合适的,除非你确切地知道你在做什么,并且不能手持编译器来制作与纯C或内部函数一样好的asm。 使用内部函数进行手动矢量化当然仍然有其位置;编译器在某些事情上仍然很糟糕,比如自动矢量化复杂的洗牌。 GCC/Clang根本不会像memchr的纯C实现那样自动矢量化搜索循环,或者任何在第一次迭代之前不知道行程计数的循环。

当然,当前微架构的性能必须胜过可维护性,并为未来的CPU进行不同的优化。 如果合适,则仅适用于程序花费大量时间且通常受 CPU 限制的小型热循环。 如果内存受限,那么通常没有太多收获。

在大规模情况下,编译器非常出色(尤其是在链接时间优化方面)。 人类无法在这种规模上竞争,不能同时保持代码的可维护性。 人类唯一可以竞争的地方是在小规模上,你可以有时间考虑循环中的每一条指令,这些指令将在程序过程中运行多次迭代。

您的代码使用越广泛且对性能越敏感(例如,像 x264 或 x265 这样的视频编码器),就越有理由考虑手动调整 asm 来处理任何事情。 每天在数百万台运行代码的计算机上节省几个周期开始加起来值得考虑维护/测试/可移植性的缺点。


一个值得注意的例外是ARM SIMD(NEON),编译器通常仍然很糟糕。 我认为特别是对于 32 位 ARM(其中每个 128 位q0..15寄存器都由 2 个 64 位d0..32寄存器混叠,因此您可以通过将 2 个半部分作为单独的寄存器访问来避免洗牌。 编译器不能很好地对此进行建模,并且在编译您希望能够高效编译的内部函数时很容易搬起石头砸自己的脚。编译器擅长从 x86 (SSE/AVX) 和 PowerPC (altivec) 的 SIMD 内部函数中生成高效的 asm,但由于某种未知原因,编译器在优化 ARM NEON 内部函数方面做得很差,并且经常产生次优的 asm。

有些编译器还不错,例如,显然 Apple clang/LLVM for AArch64 比以前更频繁地运行良好。 但是,仍然可以看到 Arm Neon Intrinsics vs 手工组装 - Jake Lee 在 2017 年 12 月发现他的 4x4 浮子矩阵的内在版本比他使用 clang 的手写版本慢 3 倍。 Jake 是 ARM 优化专家,所以我倾向于相信这是相当现实的。


__asm(在 VC++ 的情况下)

MSVC 样式的 asm 通常只对编写整个循环有用,因为必须通过内存操作数获取输入会破坏(某些)好处。 因此,在整个循环中摊销开销会有所帮助。

对于包装单个指令,引入额外的存储转发延迟是愚蠢的,并且对于几乎所有您无法在纯 C 中轻松表达的内容都有 MSVC 内联函数。 请参阅"asm"、"__asm"和"__asm__"之间有什么区别?对于单个指令的示例:如果您从大局来看(包括 ASM 块之外的编译器生成的 ASM),使用 MSVC 内联 ASM 比纯 C 或内在函数更糟糕。


C++测试Collatz猜想的代码比手写汇编更快 - 为什么?展示了一个具体的例子,其中手写的ASM在当前的CPU上比我通过调整C源能够让GCC或clang发出的任何内容都快。 他们显然不知道如何优化低延迟 LEA 当它是循环承载依赖链的一部分时。

(最初的问题有一个很好的例子,说明为什么你不应该在asm中手写,除非你确切地知道你在做什么并使用优化的编译器输出作为起点。 但我的回答表明,对于一个长期运行的热紧密循环,编译器仅通过微优化就缺少了显着的收益,甚至撇开算法改进不谈。

如果您正在考虑 asm,请始终将其与编译器发出的最佳内容进行基准测试。 使用手写的 asm 版本可能会给你一些想法,你可以将这些想法应用到你的 C 中,让编译器制作出更好的 asm。 然后,您可以获得好处,而无需代码中实际包含任何不可移植的内联 asm。

最新更新