空__asm__
语句是不够的:更好地使用数据依赖项
像这样:
main.c
int main(void) {
unsigned i;
for (i = 0; i < 10; i++) {
__asm__ volatile("" : "+g" (i) : :);
}
}
编译和反汇编:
gcc -O3 -ggdb3 -o main.out main.c
gdb -batch -ex 'disas main' main.out
输出:
0x0000000000001040 <+0>: xor %eax,%eax
0x0000000000001042 <+2>: nopw 0x0(%rax,%rax,1)
0x0000000000001048 <+8>: add $0x1,%eax
0x000000000000104b <+11>: cmp $0x9,%eax
0x000000000000104e <+14>: jbe 0x1048 <main+8>
0x0000000000001050 <+16>: xor %eax,%eax
0x0000000000001052 <+18>: retq
我相信这是可靠的,因为它在循环变量上放置了显式的数据依存关系,i
如以下建议:在C ++中强制执行语句顺序并生成所需的循环:
这标记i
为内联汇编的输入和输出。然后,内联汇编对于GCC来说是一个黑匣子,它不知道它是如何修改的i
,因此我认为确实无法对其进行优化。
如果我对空白执行相同操作__asm__
:
坏
int main(void) {
unsigned i;
for (i = 0; i < 10; i++) {
__asm__ volatile("");
}
}
它似乎完全消除了循环并输出:
0x0000000000001040 <+0>: xor %eax,%eax
0x0000000000001042 <+2>: retq
还要注意,__asm__("")
并且__asm__ volatile("")
应该相同,因为没有输出操作数:asm,asm volatile和破坏内存之间的区别
如果我们将其替换为:
__asm__ volatile("nop");
产生:
0x0000000000001040 <+0>: nop
0x0000000000001041 <+1>: nop
0x0000000000001042 <+2>: nop
0x0000000000001043 <+3>: nop
0x0000000000001044 <+4>: nop
0x0000000000001045 <+5>: nop
0x0000000000001046 <+6>: nop
0x0000000000001047 <+7>: nop
0x0000000000001048 <+8>: nop
0x0000000000001049 <+9>: nop
0x000000000000104a <+10>: xor %eax,%eax
0x000000000000104c <+12>: retq
因此,在这种情况下,我们看到GCC只是循环展开了nop
循环,因为循环足够小。
因此,如果您依赖一个Empty __asm__
,您将很难预测GCC二进制大小/速度的折衷,如果以最佳方式应用,它将始终删除__asm__ volatile("");
代码大小为零的empty的循环。
noinline
忙循环功能
如果在编译时不知道循环大小,则不可能完全展开,但是GCC仍可以决定分块展开,这会使您的延迟不一致。
将其与Denilson的答案一起,可以将繁忙循环函数编写为:
void __attribute__ ((noinline)) busy_loop(unsigned max) {
for (unsigned i = 0; i < max; i++) {
__asm__ volatile("" : "+g" (i) : :);
}
}
int main(void) {
busy_loop(10);
}
在以下位置拆卸
Dump of assembler code for function busy_loop:
0x0000000000001140 <+0>: test %edi,%edi
0x0000000000001142 <+2>: je 0x1157 <busy_loop+23>
0x0000000000001144 <+4>: xor %eax,%eax
0x0000000000001146 <+6>: nopw %cs:0x0(%rax,%rax,1)
0x0000000000001150 <+16>: add $0x1,%eax
0x0000000000001153 <+19>: cmp %eax,%edi
0x0000000000001155 <+21>: ja 0x1150 <busy_loop+16>
0x0000000000001157 <+23>: retq
End of assembler dump.
Dump of assembler code for function main:
0x0000000000001040 <+0>: mov $0xa,%edi
0x0000000000001045 <+5>: callq 0x1140 <busy_loop>
0x000000000000104a <+10>: xor %eax,%eax
0x000000000000104c <+12>: retq
End of assembler dump.
这里volatile
需要将组件标记为可能具有副作用,因为在这种情况下,我们具有输出变量。
双循环版本可能是:
void __attribute__ ((noinline)) busy_loop(unsigned max, unsigned max2) {
for (unsigned i = 0; i < max2; i++) {
for (unsigned j = 0; j < max; j++) {
__asm__ volatile ("" : "+g" (i), "+g" (j) : :);
}
}
}
int main(void) {
busy_loop(10, 10);
}
GitHub上游。
相关主题:
已在Ubuntu 19.04,GCC 8.3.0中测试。