汇编代码的长度可以指示执行速度吗?

时间:2016-06-13 10:02:27

标签: c assembly clang

我正在学习C,请考虑以下代码段:

#include <stdio.h>

int main(void) {
  int fahr;
  float calc;

  for (fahr = 300; fahr >= 0; fahr = fahr - 20) {
    calc = (5.0 / 9.0) * (fahr - 32);
    printf("%3d %6.1f\n", fahr, calc);
  }

  return 0;
}

将Celsius到华氏温度转换表从300打印到0.我用以下代码编译:

$ clang -std=c11 -Wall -g -O3 -march=native main.c -o main

我还使用此命令生成汇编代码:

$ clang -std=c11 -Wall -S -masm=intel -O3 -march=native main.c -o main

生成1.26kb文件和71行。

我稍微编辑了代码并将逻辑移动到另一个函数中,该函数在main()中被初始化:

#include <stdio.h>

void foo(void) {
  int fahr;
  float calc;

  for (fahr = 300; fahr >= 0; fahr = fahr - 20) {
    calc = (5.0 / 9.0) * (fahr - 32);
    printf("%3d %6.1f\n", fahr, calc);
  }
}

int main(void) {
  foo();
  return 0;
}

这将产生2.33kb的汇编代码,包含128行。

使用time ./main运行这两个程序我发现执行速度没有区别。

我的问题是,尝试按汇编代码的长度优化C程序是否重要?

3 个答案:

答案 0 :(得分:12)

您似乎正在比较GCC生成的.S文件的大小,因为这显然毫无意义,我只是假装您遇到的二进制大小为2,GCC生成的代码片段。

虽然在所有其他条件相同的情况下,较短的代码大小可能会提高速度(由于更高的代码密度),但通常x86 CPU足够复杂,需要在代码大小优化和优化之间进行解耦代码速度。

具体来说,如果你瞄准代码速度,你应该优化代码速度。有时这需要选择最短的片段,有时则不需要。

考虑编译器优化的经典示例,乘以2的幂:

int i = 4;
i = i * 8;

这可能被严重翻译为:

;NO optimizations at all

mov eax, 4        ;i = 4        B804000000       0-1 clocks
imul eax, 8       ;i = i * 8    6BC009           3 clocks
                  ;eax = i      8 bytes total    3-4 clocks total

;Slightly optimized
;4*8 gives no sign issue, we can use shl

mov eax, 4        ;i = 4        B804000000       0-1 clocks
shl eax, 3        ;i = i * 8    C1E003           1 clock
                  ;eax = i      8 bytes total    1-2 clocks total

两个片段具有相同的代码长度,但第二个片段的速度几乎是两倍。

这是一个非常基本的例子 1 ,其中甚至没有太多需要考虑微架构。

另一个更微妙的例子如下,取自Agner Fog对部分寄存器档位的讨论 2

;Version A                        Version B

mov al, byte ptr [mem8]           movzx ebx, byte ptr [mem8]
mov ebx, eax                      and eax, 0ffffff00h
                                  or ebx, eax

;7 bytes                           14 bytes

两个版本都给出相同的结果,但版本B 版本A 快5-6个时钟,尽管前者是后者的两倍。

答案是不,代码大小不够;它可能是一个打破平局

如果您真的对优化装配感兴趣,您将享受这两个读数:

第一个链接还有一本优化C和C ++代码的手册。

如果用C语言编写,请记住影响最大的优化是1)数据的表示/存储方式,即数据结构2)数据的处理方式,即算法。
有宏优化。

考虑到生成的程序集正在转向微优化,最有用的工具是1)智能编译器2)一组很好的内在函数 3

1 在实践中如此简单地进行优化 2 现在可能有点过时,但它有助于达到目的 3 内置的非标准功能,可转换为特定的装配说明。

答案 1 :(得分:3)

与往常一样,答案是&#34;它取决于&#34;。有时使代码更长可以提高效率:例如,CPU不必在每次循环后浪费额外的指令。一个经典的例子(字面意思是&#39;经典&#39;:1983!)是"Duff's Device"。以下代码

f
使用这个更多更大,更复杂的代码,

更快

register short *to, *from;
register count;
{
    do {                          /* count > 0 assumed */
        *to = *from++;
    } while(--count > 0);
}

但这可以走极端:使代码过大会增加缓存未命中和各种其他问题。简而言之:&#34;过早的优化是邪恶的&#34; - 在决定这是一个好主意之前,你需要在之前和之后,经常在多个平台上测试你。

我会问你:上面代码的第二个版本&#34;更好&#34;比第一个版本?它的可读性较差,维护性较差,并且比它所替代的代码复杂得多。

答案 2 :(得分:2)

在内联之后,实际运行的代码在两种情况下都是相同的。第二种方式更大,因为它还必须发出函数的独立定义,而不是内联到main

如果你在函数上使用static,你就可以避免这种情况,因此编译器会知道没有任何东西可以从编译单元外部调用它,因此一个独立的定义不是&#39 ;如果它被内联到其唯一的来电者中,则需要。

此外,编译器输出中的大多数.s行是注释或汇编程序指令,而不是指令。因此,您甚至不计算说明。

Godbolt编译器浏览器是查看编译器asm输出的好方法,只需要指令和实际使用的标签。看看your code there

如果存在循环或分支,则计算可执行文件中的指令总数完全是假的。或者特别是在循环内调用函数,就像在这种情况下一样动态指令计数(实际运行了多少指令,即每次循环计数等等)与性能非常粗略相关,但某些代码每周期运行4条指令,有些运行远低于1(例如大量的div或sqrt,缓存未命中和/或分支错误预测)。

要详细了解代码运行缓慢或快速的原因,请参阅代码wiki,尤其是Agner Fog's stuff

我最近还写了Deoptimizing a program for the pipeline in Intel Sandybridge-family CPUs的答案。考虑恶魔般地使程序运行得慢的方法是一种有趣的练习。