c - 如何证明或反驳编译的效率?

标签 c assembly

这是一个不寻常的问题,但我确实希望有一个明确的答案。

关于编译器生成代码的效率,特别是指令的数量,我们的办公室长期以来一直存在争论。我们为几乎没有循环的低功耗嵌入式系统编写代码。因此,发出的指令数与消耗的功率成正比。

我们的大部分代码看起来像这样(注意,没有动态内存分配,没有系统调用,很少有函数调用,很少有循环)。

foo += 3 * (77 + bar);
if (baz > 18 - qux)
    bar -= 19 + 7 >> spam;

我可以用 -O3 编译上面的代码片段并读取程序集,但我不能自己写。

我想证明或反驳的说法是,与手写汇编代码相比,编译器生成的代码“更胖”2-4 倍(因此消耗的功率是手写汇编代码的 2-4 倍)。

我对您使用过的任何编译器都感兴趣。

来自 this answer我知道 GCC 和 clang 可以发出与 C 代码交错的程序集

gcc -g -c -Wa,-alh foo.cc

这些答案提供了坚实的基础:

When is assembly faster?

Why do you program in assembly?

如何衡量编译器生成代码的效率?

最佳答案

手工汇编即使不能打败编译器,也至少可以与编译器相媲美,因为至少,您可以从编译器生成的汇编代码开始,并对其进行调整以使其变得更好。要真正做好工作,您需要了解 CPU 架构(流水线、功能单元、内存层次结构、乱序调度单元等),以便您可以调度每条指令以实现最高效率。

另一件需要考虑的事情是指令数量不一定与性能成正比,无论是速度还是功率(参见 Hennessey 和 Patterson 的 Computer Architecture: A Quantitative Approach )。基本上,除了指令数量(和时钟速率)之外,您还必须查看每条指令需要多少个时钟周期才能知道需要多长时间。要知道将消耗多少能量,您还需要知道每条指令需要多少能量。

CPU 执行每条指令的方式会影响执行所需的周期数。例如,您的代码序列有一个 >>> 运算符。编译器可能会将其转换为单个 ASR 指令,但在不知道架构的情况下,无法确定可能需要多少个时钟周期——一些架构可以在单个周期内进行任意移位,而其他的每个位移位需要一个周期。

内存访问也会影响循环次数和功耗。当有太多变量无法存储在寄存器中时,其中一些将不得不存储在内存中。如果您正在访问片外内存并且具有相当高的 CPU 时钟速率,则内存总线可能非常耗电。避免读取和写入内存的较长指令序列(例如,通过两次计算相同的结果)可能更便宜。

正如其他一些人所建议的那样,基准测试是无可替代的。假设您使用的是具有恒定输入电压的基于微 Controller 的系统,最好的办法是用每组替代代码测量系统的电流消耗,看看哪个最好(一种方法是使用电流探头和数字存储示波器)。

即使你总能写出比编译器更好的汇编程序,但在开发时间和可维护性方面也是有代价的。在 The Mythical Man Month Brooks 估计,当许多(如果不是大多数)程序员用汇编语言编写代码时,工作量会增加 3-5 倍。除非您的代码真的很小,否则您最好只编写汇编中最关键的部分。即便如此,编写程序集的人应该能够通过比较运行代码与运行代码来证明他们的(更昂贵的)代码值得付出代价。

关于c - 如何证明或反驳编译的效率?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18730922/

相关文章:

assembly - x86组件: Why Do I Need Stack Frames?

c - 如何理解windows ddk中的 "NTSTATUS"、 "NT_SUCCESS"typedef?

c - 段错误 - sscanf 到 C 中的数组

c - strcspn 中的 "c"代表什么?

c++ - x64 汇编、ret 寄存器和变量

security - 更改BIOS代码/刷新BIOS

c - 为什么 "for( i = 0.1 ; i != 1.0 ; i += 0.1)"在 i = 1.0 时不中断?

c - 如何创建类似 printf 变量参数的函数

c - 我从汇编代码到 C 代码的转换是否正确?

c++ - GCC 代码生成器:pthread_create_key() 与 std::shared_ptr 复制有什么关系?