在gcc中生成ASM代码需要什么,g++
What is the need to generate ASM code in gcc, g++
为了缩小我的问题范围,让我描述一下我的假设和我所做的实验。。。
我的假设是: 用汇编语言编写的代码将比其对应的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中从头开始重写所有逻辑,以获得速度和程序大小的优势?
这是一个古老的传统(在20世纪70年代的早期Unix系统上,机器非常小,生成一些汇编文件更简单),一些编译器可以直接生成对象文件或机器代码;可能是Clang/LLVM或TinyCC的最新版本(仅适用于C:编译时间快,但可执行文件非常慢!)可能是IBM的一些专有XLC编译器,GCC社区中的一些人正在考虑这一点(尤其是GCCJIT)。
但是,对于编译器开发人员来说,生成汇编文件通常更容易。由于大多数编译器工作都发生在优化过程中(转换编译器中的一些内部表示),因此损失几毫秒启动汇编程序并不是很重要。
使用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获得数百个编译器转储文件,解释编译器对代码所做的转换。
BTW今天的(超标量、流水线)处理器(桌面、笔记本电脑、平板电脑、服务器中的处理器)非常复杂,在实践中,编译器可以比人类程序员更好地进行优化。因此,实际上由优化编译器从一些实际大小的C代码(例如,几百行的C源文件)生成的汇编程序代码通常比实验汇编程序人类程序员在几周内(不到一千行汇编程序行)编写的快。换句话说,您的假设(用汇编程序编写的代码比用C编写的代码更快/更好,并由优秀的优化编译器编译)在实践中是错误的。
(顺便说一句,优化编译器可以将没有可观察到的副作用(例如没有输入和输出)的bin2dec.c
程序转换为空程序,而GCC 5.2对gcc -O2
做到了这一点!)
阅读关于停顿问题和Rice定理。优化编译器或静态程序分析器所能实现的功能存在固有的局限性。
假设:用汇编语言编写的代码将比它的运行速度快得多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,并将其与我可能是最好的手写asm进行比较。我试图让gcc生成类似的最优输出,但没有成功。它要么使用多个分支,要么使用两个cmov指令(这会使无箝位快速路径变慢),而不是用于箝位或不用于箝位的分支,然后是用于箝位到零或箝位到最大值的cmov。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 警告处理为错误这里有什么问题
- 什么时候调用组成单元对象的析构函数
- #定义c-预处理器常量..我做错了什么
- 努力将整数转换为链表。不知道我在这里做错了什么
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 什么时候在C++中返回常量引用是个好主意
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- C++避免重复声明的语法是什么
- c++库的公共头文件中应该包含什么
- 问题:什么是QAbstractItemView::NoEditTriggers的反面
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- ifstream什么都没读
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- C++从另一个类访问公共静态向量的正确方法是什么
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 我应该使用什么来代替void作为变体中的替代类型之一
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用