在gcc中生成ASM代码需要什么,g++

What is the need to generate ASM code in gcc, g++

本文关键字:什么 g++ 代码 ASM gcc      更新时间:2023-10-16

为了缩小我的问题范围,让我描述一下我的假设和我所做的实验。。。

我的假设是: 用汇编语言编写的代码将比其对应的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 -timegcc -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。