了解汇编程序的原因之一是,有时可以使用汇编程序来编写比用高级语言(特别是C语言)编写的代码性能更好的代码。然而,我也听人说过很多次,尽管这并非完全错误,但实际上可以使用汇编程序来生成性能更好的代码的情况极其罕见,并且需要汇编方面的专业知识和经验。

这个问题甚至没有涉及到这样一个事实,即汇编程序指令将是特定于机器的、不可移植的,或者汇编程序的任何其他方面。当然,除了这一点之外,了解汇编还有很多很好的理由,但这是一个需要示例和数据的具体问题,而不是关于汇编程序与高级语言的扩展论述。

谁能提供一些具体的例子,说明使用现代编译器汇编代码比编写良好的C代码更快,并且您能否用分析证据支持这一说法?我相信这些案例确实存在,但我真的很想知道这些案例到底有多深奥,因为这似乎是一个有争议的问题。


当前回答

不需要给出任何具体的示例或分析器证据,当您比编译器知道的更多时,您可以编写比编译器更好的汇编程序。

In the general case, a modern C compiler knows much more about how to optimize the code in question: it knows how the processor pipeline works, it can try to reorder instructions quicker than a human can, and so on - it's basically the same as a computer being as good as or better than the best human player for boardgames, etc. simply because it can make searches within the problem space faster than most humans. Although you theoretically can perform as well as the computer in a specific case, you certainly can't do it at the same speed, making it infeasible for more than a few cases (i.e. the compiler will most certainly outperform you if you try to write more than a few routines in assembler).

另一方面,有些情况下编译器没有那么多的信息——我想说主要是在使用不同形式的外部硬件时,编译器不知道这些信息。主要的例子可能是设备驱动程序,其中汇编程序结合人类对相关硬件的熟悉知识可以产生比C编译器更好的结果。

其他人提到了特殊用途指令,这就是我在上面一段中所说的——编译器可能对这些指令了解有限或根本不了解,这使得人类可以编写更快的代码。

其他回答

如果您没有查看编译器生成的内容的反汇编,您实际上无法知道编写良好的C代码是否真的很快。很多时候你会发现“写得好”是主观的。

因此,没有必要用汇编程序来获得最快的代码,但出于同样的原因,了解汇编程序当然是值得的。

下面是一个真实的例子:固定点在旧编译器上进行乘法运算。

这些不仅在没有浮点数的设备上很方便,在精度方面也很出色,因为它们可以提供32位精度和可预测的错误(浮点数只有23位,很难预测精度损失)。即在整个范围内均匀的绝对精度,而不是接近均匀的相对精度(浮点数)。


现代编译器很好地优化了这个定点示例,因此对于仍然需要特定于编译器的代码的更现代的示例,请参见

获得64位整数乘法的高部分:使用uint64_t for 32x32 => 64位乘法的便携版本在64位CPU上无法优化,因此你需要intrinsic或__int128来在64位系统上实现高效的代码。 Windows 32位上的_umul128: MSVC在将32位整数转换为64时并不总是做得很好,因此intrinsic有很大帮助。


C语言没有完整的乘法运算符(由n位输入产生2n位)。在C语言中表达它的通常方法是将输入转换为更宽的类型,并希望编译器能够识别输入的上半部分是不有趣的:

// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
  long long a_long = a; // cast to 64 bit.

  long long product = a_long * b; // perform multiplication

  return (int) (product >> 16);  // shift by the fixed point bias
}

这段代码的问题在于,我们做了一些不能直接用c语言表达的事情。我们希望将两个32位的数字相乘,得到一个64位的结果,并返回中间的32位。然而,在C语言中这个乘法是不存在的。您所能做的就是将整数提升为64位,并执行64*64 = 64乘法。

x86(以及ARM、MIPS和其他)可以在一条指令中完成乘法运算。一些编译器过去常常忽略这一事实,并生成调用运行时库函数来进行相乘的代码。移位到16也经常由库例程完成(x86也可以做这样的移位)。

所以我们只剩下一两个乘法库调用。这造成了严重的后果。不仅移位速度较慢,而且在整个函数调用中必须保留寄存器,而且对内联和展开代码也没有帮助。

如果你在(内联)汇编器中重写相同的代码,你可以获得显著的速度提升。

除此之外:使用ASM并不是解决问题的最佳方法。大多数编译器允许你以内在的形式使用一些汇编指令,如果你不能用c语言表达它们。例如,VS.NET2008编译器将32*32=64位的mul公开为__emul,将64位的移位公开为__ll_rshift。

使用intrinsic,你可以以一种c编译器有机会理解发生了什么的方式重写函数。这允许代码内联,寄存器分配,公共子表达式消除和常量传播也可以完成。与手工编写的汇编程序代码相比,您将获得巨大的性能改进。

供参考:VS.NET编译器的定点mul的最终结果是:

int inline FixedPointMul (int a, int b)
{
    return (int) __ll_rshift(__emul(a,b),16);
}

定点除法的性能差异更大。通过编写几行asm代码,我对除法重的定点代码进行了10倍的改进。


使用Visual c++ 2013为这两种方式提供了相同的汇编代码。

2007年的gcc4.1也很好地优化了纯C版本。(Godbolt编译器资源管理器没有安装任何早期版本的gcc,但即使是较旧的gcc版本也可以在没有intrinsic的情况下做到这一点。)

在Godbolt编译器资源管理器上查看用于x86(32位)和ARM的source + asm。(不幸的是,它没有任何旧到足以从简单的纯C版本生成糟糕代码的编译器。)


现代cpu可以做一些C语言根本没有操作符的事情,比如popcnt或位扫描来查找第一个或最后一个设置位。POSIX有一个ffs()函数,但是它的语义不匹配x86 bsf / bsr。见https://en.wikipedia.org/wiki/Find_first_set)。

一些编译器有时可以识别一个计数整数中设置位数的循环,并将其编译为popcnt指令(如果在编译时启用),但在GNU C中使用__builtin_popcnt要可靠得多,或者在x86上(如果你的目标硬件是SSE4.2: _mm_popcnt_u32 from < immintrinh >)。

或者在c++中,赋值给std::bitset<32>并使用.count()。(在这种情况下,该语言已经找到了一种方法,可以通过标准库可移植地公开popcount的优化实现,以一种总是编译为正确的方式,并且可以利用目标支持的任何东西。)参见https://en.wikipedia.org/wiki/Hamming_weight#Language_support。

类似地,ntohl可以在一些具有它的C实现上编译为bswap(用于端序转换的x86 32位字节交换)。


intrinsic或手写asm的另一个主要领域是使用SIMD指令进行手工向量化。编译器对于dst[i] += src[i] * 10.0;这样的简单循环并不糟糕,但是当事情变得更复杂时,编译器通常做得很糟糕,或者根本不自动向量化。例如,你不太可能得到任何像如何实现atoi使用SIMD?由编译器从标量代码自动生成。

Actually you can build large scale programs in a large model mode segaments may be restricted to 64kb code but you can write many segaments, people give the argument against ASM as it is an old language and we don't need to preserve memory anymore, If that were the case why would we be packing our PC's with memory, the only Flaw I can find with ASM is that it is more or less Processor based so most programs written for the intel architecture Most likely would not run on An AMD Architecture. As for C being faster than ASM there is no language faster than ASM and ASM can do many thing's C and other HLL's can not do at processor level. ASM is a difficult language to learn but once you learn it no HLL can translate it better than you. If you could only see some of the things HLL's Do to you code, and understand what it is doing, you would wonder why More people don't use ASM and why assembers are no longer being updated ( For general public use anyway). So no C is not faster than ASM. Even experiences C++ programmers still use and write code Chunks in ASM added to there C++ code for speed. Other Languages Also that some people think are obsolete or possibly no good is a myth at times for instance Photoshop is written in Pascal/ASM 1st release of souce has been submitted to the technical history museum, and paintshop pro is written still written in Python,TCL and ASM ... a common denominator of these to "Fast and Great image processors is ASM, although photoshop may have Upgraded to delphi now it is still pascal. and any speed problems are comming from pascal but this is because we like the way programs look and not what they do now days. I would like to make a Photoshop Clone in pure ASM which I have been working on and its comming along rather well. not code,interpret,arange,rewwrite,etc.... Just code and go process complete.

C语言常常需要做一些从汇编编码员的角度看来不必要的事情,这只是因为C标准这么说。

例如,整数提升。如果你想在C语言中移动一个char变量,人们通常会期望代码实际上只做一个比特的移动。

然而,标准强制编译器在移位之前将符号扩展为int,然后将结果截断为char,这可能会使代码复杂化,这取决于目标处理器的架构。

这完全取决于你的工作量。

对于日常操作,C和c++已经很好了,但是有一些特定的工作负载(任何涉及视频的转换(压缩、解压缩、图像效果等))几乎需要组装才能达到性能。

它们通常还涉及使用特定于CPU的芯片组扩展(MME/MMX/SSE/等等),这些扩展是为这些类型的操作而优化的。