在gcc、g++中生成ASM代码需要啥
Posted
技术标签:
【中文标题】在gcc、g++中生成ASM代码需要啥【英文标题】:What is the need to generate ASM code in gcc, g++在gcc、g++中生成ASM代码需要什么 【发布时间】:2015-12-01 10:14:27 【问题描述】:为了缩小我的问题范围,让我描述一下我的假设和我所做的实验......
我的假设: 用汇编语言编写的代码将比其 C/C++ 对应的代码运行得快得多,而且可执行文件的大小也比从 C 生成的要小得多/C++ 代码。
实验:我在bin2dec.c中写了下面的程序
#include <stdio.h>
int main()
long int binary, decimal, reminder, exp;
int i, j;
for(i=0; i<10000; i++)
for(j=0; j<1000; j++)
binary = 11000101;
exp = 1;
decimal = 0;
while(binary != 0)
reminder = binary % 10;
binary = binary / 10;
decimal = decimal + reminder * exp;
exp *= 2;
return 0;
然后为其生成ASM代码gcc -S bin2dec.c -o bin2dec.s
之后我编译了两个文件如下
gcc bin2dec.c -o bin2dec_c
gcc bin2dec.s -o bin2dec_s
测试 1:找出两个文件的一些内部细节
[guest@localhost ASM]$ size bin2dec_c bin2dec_s
text data bss dec hex filename
951 252 4 1207 4b7 bin2dec_c
951 252 4 1207 4b7 bin2dec_s
结果:两者完全相同...
测试 2:执行文件并计算所用时间
[guest@localhost ASM]$ time ./bin2dec_c
real 0m1.724s
user 0m1.675s
sys 0m0.002s
[guest@localhost ASM]$ time ./bin2dec_s
real 0m1.721s
user 0m1.676s
sys 0m0.001s
结果:两者相同。有时,从 ASM 生成的可执行文件运行速度较慢 :-(
所以问题是,我的假设是否错误? 如果不是,我犯了什么错误使可执行文件 bin2dec_c 和 bin2dec_s 以相同的速度运行? 有没有更好的方法从 C/C++ 程序中获取 ASM 代码,或者我应该从头开始重写 ASM 中的所有逻辑以获得速度和程序大小的优势?
【问题讨论】:
谁说“必须这样做”? 我不确定你的意思。 如果您不需要该程序集,请不要使用它。有什么问题? 有时人类查看 ASM 而不必反汇编 exe 会很有用 您忘记了-O2
选项到 gcc
【参考方案1】:
这是一个古老的传统(在 1970 年代早期的 Unix 系统上,机器很小,生成一些汇编文件比较简单),一些编译器可以直接生成目标文件或机器代码;可能是 Clang/LLVM 或 TinyCC 的最新版本(仅适用于 C:编译时间快,但可执行文件非常慢!)也许是 IBM 的一些专有 XLC 编译器,GCC 社区中的一些人正在考虑这个问题(尤其是GCCJIT)。
但是,编译器开发人员通常更容易生成汇编文件。而且由于大部分编译器工作发生在optimization 传递中(它们在编译器中转换一些内部表示),因此启动汇编器时损失几毫秒并不是很重要。
使用GCC,使用gcc -time
和gcc -ftime-report
(当然还有你常用的优化标志,例如-O2
)进行编译,以了解编译器将时间花在哪里。它永远不会在汇编程序中......
您有时会发现查看生成的汇编文件很有用。使用g++ -O2 -Wall -S -fverbose-asm -std=c++11 foo.cc
编译您的foo.cc
C++11 文件,然后查看(使用一些编辑器或寻呼机)生成的foo.s
汇编程序文件。
您甚至可以使用g++ -fdump-tree-all -O2
进行编译,并从GCC 获取数百个编译器转储文件,解释编译器对您的代码所做的转换。
顺便说一句,今天的(超标量、流水线)处理器(台式机、笔记本电脑、平板电脑、服务器中的处理器)非常复杂,实际上编译器可以比人类程序员进行更好的优化。所以实际上由一个优化编译器从一些实际大小的 C 代码(例如几百行的 C 源文件)生成的汇编代码是 > 通常比经过试验的汇编程序人类程序员在几周内编写的代码要快(不到一千行汇编程序行)。换句话说,您的假设(用汇编程序人工编写的代码比用 C 语言编写并由良好的优化编译器编译的代码更快/更好)是错误 在实践中。
(顺便说一句,优化编译器可以将您的bin2dec.c
程序转换为一个空程序,该程序没有可观察到的副作用,例如没有输入和输出,而GCC 5.2 使用@987654339 执行此操作@!!)
另请阅读halting problem 和Rice's theorem。优化编译器或static program analyzers 可以实现的功能存在内在限制。
【讨论】:
能够生成汇编代码对于理解生成的代码和调试可能的编译器错误很重要。不需要额外的努力来直接生成机器代码而不经过汇编,这可能是一些编译器供应商避免打扰的事情。 @DavidSchwartz:更重要的是,您可以使用 gcc/clang 的-fverbose-asm
之类的编译器选项,编译器将根据决定生成该 asm 的方式将 cmets 添加到 asm。 (例如,哪个源变量名称被加载到寄存器中)。仅仅编译成机器码然后反汇编是不容易做到的。
现代无序 CPU 实际上比 Pentium (586) 或 Atom(或有序 RISC 机器)等有序超大规模/流水线 CPU 更容易优化。调度指令可能会产生很小的影响,但主要是您只需要公开并行性,考虑您正在创建的依赖链与考虑您需要跟踪以编写代码的数据流相差不远.【参考方案2】:
假设:用汇编语言编写的代码将比它的运行速度快得多 C/C++ 对应物,而且可执行文件的大小要小得多 从 C/C++ 代码生成。
汇编语言只是机器代码的文本表示。
有一些注意事项,您可以反汇编一个二进制文件,然后将该源重新组装回同一个二进制文件。显然这对于 ARM 来说确实是可能的,但是 x86 asm 方言没有语法来表示同一指令的不同编码。例如在 PLT(过程链接表)中的 jmp
指令中强制使用 4 字节偏移量,跳转目标将在稍后修补。
您的实验制作了两个完全相同的二进制文件。 gcc 直接从 C 到可执行文件在内部生成一个 asm 源文件并组装它。您只需拆分流程,以便获得编译器生成的 asm。
手写汇编代码总是至少与编译器输出一样好。您始终可以从编译器输出开始并寻求改进。在极少数情况下,不会有任何改进。
但是,在编译过程中简单地观察编译器生成的 asm 并不能改善它!将您的代码插入 http://gcc.godbolt.org/ 以查看各种不同编译器的输出(甚至对于 ARM 或 PPC,这对于 std:atomic
代码很有趣,以查看弱排序拱上会发生什么)
由于您在没有优化的情况下进行编译,因此肯定会有很大的改进。我将从gcc -O3 -march=native -fverbose-asm -masm=intel -S
的输出开始
非常很少有编译器输出真正达到最优,即使对于短序列也是如此。编译器对人类的优势在于一次跟踪大量源代码,并根据他们可以跨函数证明的东西进行优化。 (这样的全程序优化太脆弱了,人类无法在源代码中维护。)因此编译器可以利用在这个构建中恰好是真实的东西,但不是正在编译的函数设计的一部分。
编译器几乎总是做好工作,但很少出色工作。重要的是它是一个足够好的工作,并且代码运行速度很快,即使它使用了比需要更多的指令。通常,诸如分支错误预测、缓存未命中和依赖链之类的东西都是瓶颈,并且 CPU 足够宽,可以处理编译器倾向于使用的额外指令,而不会显着降低速度。使用超线程,用更少的指令完成同样的工作是一个更大的优势。
有关具体示例,请参阅https://codereview.stackexchange.com/questions/6502/fastest-way-to-clamp-an-integer-to-the-range-0-255 上的编译器输出,并将其与我的probably-optimal hand-written asm 进行比较。我试图让 gcc 生成类似的最佳输出,但没有成功。它要么使用多个分支,要么使用两个 cmov 指令(这会使无钳位快速路径变慢),而不是用于钳位的分支,然后是用于钳位为零或钳位最大值的 cmov。
【讨论】:
您好 Peter,Basile,感谢您花时间和精力回答我的问题。我学到的是,我最初的假设是错误的。但是,我刚刚遇到menuetos.net,人们正试图在汇编语言上开发整个操作系统。这只引发了我脑海中的上述问题。以上是关于在gcc、g++中生成ASM代码需要啥的主要内容,如果未能解决你的问题,请参考以下文章