GCC优化对比特操作的有效性

以下是在x86-64上设置C中的单个位的两种方法:

inline void SetBitC(long *array, int bit) { //Pure C version *array |= 1<<bit; } inline void SetBitASM(long *array, int bit) { // Using inline x86 assembly asm("bts %1,%0" : "+r" (*array) : "g" (bit)); } 

使用带有-O3 -march=core2选项的GCC 4.3,当使用常量bit时,C版本需要大约90%的时间 。 (两个版本编译为完全相同的汇编代码,除了C版本使用or [1<<num],%rax指令而不是bts [num],%rax指令)

与变量bit ,C版本表现更好,但仍然明显慢于内联汇编。

重置,切换和检查位具有类似的结果。

为什么GCC对这种常见操作的优化程度如此之差? 我是否在使用C版本做错了什么?

编辑:对不起,等待很长时间,这是我用来进行基准测试的代码。 它实际上是一个简单的编程问题…

 int main() { // Get the sum of all integers from 1 to 2^28 with bit 11 always set unsigned long i,j,c=0; for (i=1; i<(1<<28); i++) { j = i; SetBit(&j, 10); c += j; } printf("Result: %lu\n", c); return 0; } gcc -O3 -march=core2 -pg test.c ./a.out gprof with ASM: 101.12 0.08 0.08 main with C: 101.12 0.16 0.16 main 

time ./a.out也给出了类似的结果。

为什么GCC对这种常见操作的优化程度如此之差?

序言:自20世纪80年代后期以来,对编译器优化的关注已经从专注于个人操作的微基准测试转向专注于人们关注速度的应用程序的宏基准测试 。 目前,大多数编译器编写者都专注于宏基准测试,而开发良好的基准测试套件则需要认真对待。

:gcc上的任何人都没有使用基准测试,其中orbts之间的差异与实际程序的执行时间有关。 如果你可以制作这样的节目,你可能会得到gcc-land中人们的关注。

我是否在使用C版本做错了什么?

不,这是非常好的标准C.事实上,非常可读和惯用。

你可以发布你用来做时间的代码吗? 这种操作对于准确的时间来说可能是棘手的。

理论上,两个代码序列应该同样快,所以最可能的解释(在我看来)是某些东西导致你的计时代码给出虚假结果。

对于这样的代码:

 #include  #include  int main() { volatile long long i = 0; time_t start = time (NULL); for (long long n = 0; n < (1LL << 32); n++) { i |= 1 << 10; } time_t end = time (NULL); printf("C took %ds\n", (int)(end - start)); start = time (NULL); for (long long n = 0; n < (1LL << 32); n++) { __asm__ ("bts %[bit], %[i]" : [i] "=r"(i) : "[i]"(i), [bit] "i" (10)); } end = time (NULL); printf("ASM took %ds\n", (int)(end - start)); } 

结果是:

 C took 12s ASM took 10s 

我的旗帜是( -std=gnu99 -O2 -march=core2 )。 没有挥发性,循环被优化了。 gcc 4.4.2。

没有区别:

 __asm__ ("bts %[bit], %[i]" : [i] "+m"(i) : [bit] "r" (10)); 

所以可能答案是 - 没人关心。 在microbenchmark中,唯一的区别是这两种方法之间的区别,但在现实生活中,我相信这样的代码不占用太多CPU。

此外,对于此类代码:

 #include  #include  int main() { volatile long long i = 0; time_t start = time (NULL); for (long long n = 0; n < (1L << 32); n++) { i |= 1 << (n % 32); } time_t end = time (NULL); printf("C took %ds\n", (int)(end - start)); start = time (NULL); for (long long n = 0; n < (1L << 32); n++) { __asm__ ("bts %[bit], %[i]" : [i] "+m"(i) : [bit] "r" (n % 32)); } end = time (NULL); printf("ASM took %ds\n", (int)(end - start)); } 

结果是:

 C took 9s ASM took 10s 

两个结果都“稳定”。 测试CPU'Intel(R)Core(TM)2 Duo CPU T9600 @ 2.80GHz'。

这是嵌入式系统上非常常见的操作,通常是资源受限的。 10 Cycles vs 5 Cycles对这类系统来说是一个令人讨厌的性能损失。 在许多情况下,人们想要访问IO端口或使用16或32位寄存器作为布尔位标志来节省内存。

事实上, if(bit_flags& 1<<12)更具可读性[并且在使用库实现时可移植],而不是程序集等效。 同样,对于IO_PINS|= 1<<5; 不幸的是,这些速度慢很多,因此笨拙的asm宏仍然存在。

在许多方面,嵌入式和用户空间应用程序的目标是相反的。 外部通信(对用户界面或机器接口)的响应性不太重要,同时确保在最短时间内完成控制循环(等于微小标记)是绝对关键的,可以建立或破坏选定的处理器或控制战略。

显然,如果一个人能够负担得起多GHz的CPU以及所有相关的外围设备,芯片组等需要支持它,那么根本不需要担心低级优化。 实时控制系统中的1000倍速微控制器意味着节省时钟周期的重要性要高出1000倍。

我想你问了很多优化器。

您可以通过执行`register long z = 1L << bit;“然后或者使用您的数组来帮助它。

但是,我假设90%的时间,你的意思是C版本需要10个周期而asm版本需要5个周期,对吗? 性能如何在-O2或-O1进行比较?