(已编辑)什么时候应该在 c 中使用内联汇编(优化之外)?
(Edited) When should one use inline assembly in c (outside of optimization)?
注意: 编辑使问题不基于选项
假设
- 我们处于用户模式(不在内核)
- 正在使用的 OS 是 Linux 的现代版本或使用 x86 CPU.
的现代版本 windows
除了优化之外,是否有需要在 C 程序中使用内联汇编的具体示例。 (如果适用,提供内联程序集)
要清楚,通过使用关键字__asm__
(在GCC
的情况下)或__asm
(在[=13的情况下)来注入汇编语言代码=])
(这大部分是为原版问题写的,后来被编辑了)
您的意思纯粹是出于性能原因,所以不包括在 OS 内核中使用特殊指令?
您最终真正想要的是能够高效执行的机器代码。以及修改一些文本文件并重新编译以获得不同机器代码的能力。您通常可以在不需要内联 asm 的情况下获得这两个东西,因此:
https://gcc.gnu.org/wiki/DontUseInlineAsm
GNU C 内联汇编很难正确使用,但是如果您正确使用它,开销就会非常低。尽管如此,它仍然阻止了许多重要的优化,如常量传播。
有关如何高效/安全地使用它的指南,请参阅 https://whosebug.com/tags/inline-assembly/info。 (例如,使用约束而不是愚蠢的 mov
指令作为 asm 模板中的第一条或最后一条指令。)
几乎总是不合适的,除非你确切地知道你在做什么并且不能手持编译器来制作与纯 C 或内在函数一样好的 asm .使用内在函数的手动矢量化当然仍然有一席之地;编译器在某些方面仍然很糟糕,比如自动向量化复杂的洗牌。 GCC/Clang 不会自动矢量化搜索循环,例如 memchr
的纯 C 实现,或者在第一次迭代之前不知道行程计数的任何循环。
当然,当前微体系结构的性能必须胜过可维护性和针对未来 CPU 的不同优化。如果合适,仅适用于您的程序花费 很多 时间且通常 CPU 的小型热循环。如果内存有限,那么通常不会有太多收获。
在大规模上,编译器非常出色(尤其是 link 时间优化)。人类无法在这种规模上竞争,而不是在保持代码可维护性的情况下。人类唯一仍然可以竞争的地方是在小范围内,你可以花时间思考循环中的每一条指令,在整个过程中将 运行 多次 迭代一个程序。
您的代码(例如 x264 或 x265 之类的视频编码器)使用越广泛且对性能越敏感,就越有理由考虑对任何东西进行手动调整 asm。在数百万台计算机上节省几个周期 运行 每天修改您的代码开始加起来值得考虑维护/测试/可移植性缺点。
一个值得注意的例外是 ARM SIMD (NEON),其中的编译器通常仍然很糟糕。我认为尤其是对于 32 位 ARM(其中每个 128 位 q0..15
寄存器被 2x 64 位 d0..32
寄存器别名化,因此您可以通过将两半作为单独的寄存器访问来避免混洗。编译器不不能很好地建模,并且在编译您期望能够高效编译的内部函数时很容易搬起石头砸自己的脚。编译器擅长从 SIMD 内部函数为 x86 (SSE/AVX) 和 PowerPC 生成高效的 asm (altivec),但由于某些未知原因,不擅长优化 ARM NEON 内在函数,并且经常使 asm 次优。
有些编译器还不错,例如显然,AArch64 的 Apple clang/LLVM 比以前更正常。但是,请参阅 Arm Neon Intrinsics vs hand assembly - Jake Lee 在 2017 年 12 月发现他的 4x4 float matmul 的内在函数版本比他使用 clang 的手写版本慢 3 倍。Jake 是 ARM 优化专家,所以我倾向于相信这很现实。
or __asm
(in the case of VC++)
MSVC 风格的 asm 通常只对编写整个循环有用,因为必须通过内存操作数获取输入会破坏(部分)好处。因此,在整个循环中分摊开销会有所帮助。
为了包装单个指令,引入额外的存储转发延迟是愚蠢的,而且 是 MSVC 内在函数,几乎所有你不能用纯 C 轻松表达的东西。见 What is the difference between 'asm', '__asm' and '__asm__'? 对于单个指令的示例:使用 MSVC 内联 asm 得到的 asm 比使用纯 C 或内联 asm 更糟糕,如果你看大图(包括编译器在你的 asm 块之外生成的 asm)。
C++ code for testing the Collatz conjecture faster than hand-written assembly - why? 显示了一个具体示例,其中手写 asm 在当前 CPUs 上比我通过调整 C 源能够让 GCC 或 clang 发出的任何东西都快。当 LEA 是循环携带的依赖链的一部分时,他们显然不知道如何针对低延迟 LEA 进行优化。
(最初的问题有一个很好的例子说明为什么你 不应该 在 asm 中手写,除非你知道 确切 你在做什么 并使用 优化的 编译器输出作为起点。但我的回答表明,对于长期 运行 宁热紧密循环,编译器仅通过微优化就缺少了显着的收益,甚至不考虑算法改进。)
如果您正在考虑 asm,始终将其与您能让编译器发出的最佳结果进行基准测试。在手写的 asm 版本上工作可能会给您一些想法,您可以将这些想法应用于您的 C 以手持编译器来制作更好的 asm。然后,您无需 实际上 在您的代码中包含任何不可移植的内联 asm 即可获得好处。
注意: 编辑使问题不基于选项
假设
- 我们处于用户模式(不在内核)
- 正在使用的 OS 是 Linux 的现代版本或使用 x86 CPU. 的现代版本 windows
除了优化之外,是否有需要在 C 程序中使用内联汇编的具体示例。 (如果适用,提供内联程序集)
要清楚,通过使用关键字__asm__
(在GCC
的情况下)或__asm
(在[=13的情况下)来注入汇编语言代码=])
(这大部分是为原版问题写的,后来被编辑了)
您的意思纯粹是出于性能原因,所以不包括在 OS 内核中使用特殊指令?
您最终真正想要的是能够高效执行的机器代码。以及修改一些文本文件并重新编译以获得不同机器代码的能力。您通常可以在不需要内联 asm 的情况下获得这两个东西,因此:
https://gcc.gnu.org/wiki/DontUseInlineAsm
GNU C 内联汇编很难正确使用,但是如果您正确使用它,开销就会非常低。尽管如此,它仍然阻止了许多重要的优化,如常量传播。
有关如何高效/安全地使用它的指南,请参阅 https://whosebug.com/tags/inline-assembly/info。 (例如,使用约束而不是愚蠢的 mov
指令作为 asm 模板中的第一条或最后一条指令。)
几乎总是不合适的,除非你确切地知道你在做什么并且不能手持编译器来制作与纯 C 或内在函数一样好的 asm .使用内在函数的手动矢量化当然仍然有一席之地;编译器在某些方面仍然很糟糕,比如自动向量化复杂的洗牌。 GCC/Clang 不会自动矢量化搜索循环,例如 memchr
的纯 C 实现,或者在第一次迭代之前不知道行程计数的任何循环。
当然,当前微体系结构的性能必须胜过可维护性和针对未来 CPU 的不同优化。如果合适,仅适用于您的程序花费 很多 时间且通常 CPU 的小型热循环。如果内存有限,那么通常不会有太多收获。
在大规模上,编译器非常出色(尤其是 link 时间优化)。人类无法在这种规模上竞争,而不是在保持代码可维护性的情况下。人类唯一仍然可以竞争的地方是在小范围内,你可以花时间思考循环中的每一条指令,在整个过程中将 运行 多次 迭代一个程序。
您的代码(例如 x264 或 x265 之类的视频编码器)使用越广泛且对性能越敏感,就越有理由考虑对任何东西进行手动调整 asm。在数百万台计算机上节省几个周期 运行 每天修改您的代码开始加起来值得考虑维护/测试/可移植性缺点。
一个值得注意的例外是 ARM SIMD (NEON),其中的编译器通常仍然很糟糕。我认为尤其是对于 32 位 ARM(其中每个 128 位 q0..15
寄存器被 2x 64 位 d0..32
寄存器别名化,因此您可以通过将两半作为单独的寄存器访问来避免混洗。编译器不不能很好地建模,并且在编译您期望能够高效编译的内部函数时很容易搬起石头砸自己的脚。编译器擅长从 SIMD 内部函数为 x86 (SSE/AVX) 和 PowerPC 生成高效的 asm (altivec),但由于某些未知原因,不擅长优化 ARM NEON 内在函数,并且经常使 asm 次优。
有些编译器还不错,例如显然,AArch64 的 Apple clang/LLVM 比以前更正常。但是,请参阅 Arm Neon Intrinsics vs hand assembly - Jake Lee 在 2017 年 12 月发现他的 4x4 float matmul 的内在函数版本比他使用 clang 的手写版本慢 3 倍。Jake 是 ARM 优化专家,所以我倾向于相信这很现实。
or
__asm
(in the case of VC++)
MSVC 风格的 asm 通常只对编写整个循环有用,因为必须通过内存操作数获取输入会破坏(部分)好处。因此,在整个循环中分摊开销会有所帮助。
为了包装单个指令,引入额外的存储转发延迟是愚蠢的,而且 是 MSVC 内在函数,几乎所有你不能用纯 C 轻松表达的东西。见 What is the difference between 'asm', '__asm' and '__asm__'? 对于单个指令的示例:使用 MSVC 内联 asm 得到的 asm 比使用纯 C 或内联 asm 更糟糕,如果你看大图(包括编译器在你的 asm 块之外生成的 asm)。
C++ code for testing the Collatz conjecture faster than hand-written assembly - why? 显示了一个具体示例,其中手写 asm 在当前 CPUs 上比我通过调整 C 源能够让 GCC 或 clang 发出的任何东西都快。当 LEA 是循环携带的依赖链的一部分时,他们显然不知道如何针对低延迟 LEA 进行优化。
(最初的问题有一个很好的例子说明为什么你 不应该 在 asm 中手写,除非你知道 确切 你在做什么 并使用 优化的 编译器输出作为起点。但我的回答表明,对于长期 运行 宁热紧密循环,编译器仅通过微优化就缺少了显着的收益,甚至不考虑算法改进。)
如果您正在考虑 asm,始终将其与您能让编译器发出的最佳结果进行基准测试。在手写的 asm 版本上工作可能会给您一些想法,您可以将这些想法应用于您的 C 以手持编译器来制作更好的 asm。然后,您无需 实际上 在您的代码中包含任何不可移植的内联 asm 即可获得好处。