使用非常规的 for 循环更高效的 Asm? [复制]
Posted
技术标签:
【中文标题】使用非常规的 for 循环更高效的 Asm? [复制]【英文标题】:More efficient Asm with unconventional for-loop? [duplicate] 【发布时间】:2021-06-18 05:49:44 【问题描述】:我正在玩编译器资源管理器,试图了解更多关于 ARM-Assembly 的知识。我使用 arm64 msvc v19.latest。我注意到我少了一个这样的分支:
int main()
for(unsigned i = 0; i<8;)
i++;
return 0;
与编写这样的 for 循环的“常规”方式相比:
int main()
for(unsigned i = 0; i<8;i++)
;
return 0;
因此以非常规的方式编写 for 循环是否更有效?我将粘贴两个asm进行比较。先用非常规的方法:
;Flags[SingleProEpi] functionLength[52] RegF[0] RegI[0] H[0] frameChainReturn[UnChained] frameSize[16]
|main| PROC
|$LN6|
sub sp,sp,#0x10
mov w8,#0
str w8,[sp]
|$LN2@main|
ldr w8,[sp]
cmp w8,#8
bhs |$LN3@main|
ldr w8,[sp]
add w8,w8,#1
str w8,[sp]
b |$LN2@main|
|$LN3@main|
mov w0,#0
add sp,sp,#0x10
ret
ENDP ; |main|
和传统的方式:
;Flags[SingleProEpi] functionLength[56] RegF[0] RegI[0] H[0] frameChainReturn[UnChained] frameSize[16]
|main| PROC
|$LN6|
sub sp,sp,#0x10
mov w8,#0
str w8,[sp]
b |$LN4@main|
|$LN2@main|
ldr w8,[sp]
add w8,w8,#1
str w8,[sp]
|$LN4@main|
ldr w8,[sp]
cmp w8,#8
bhs |$LN3@main|
b |$LN2@main|
|$LN3@main|
mov w0,#0
add sp,sp,#0x10
ret
ENDP ; |main|
【问题讨论】:
开启优化会更加提升性能。 你编译这个没有优化吗?结果非常糟糕且不确定。 是的,您的代码没有副作用,因此好的编译器应该全部替换为return 0
启用优化时会删除整个循环。 for(unsigned i=0; i<8; ) printf("%d\n", i); i++;
和“标准”版本都会产生相同的展开循环并启用优化。
Re "在这个例子中打开优化有点失败,",不,你没有抓住重点:比较没有意义两个未优化的程序集的优化程度。
【参考方案1】:
如果您想要优化代码,请咨询您的编译器!检查未优化代码的优化程度毫无意义。
-O3
完全消除了循环。
Compiler Explorer demo: standardCompiler Explorer demo: non-standard
如果我们在循环中添加一些具有副作用的东西,我们会从两种方法中得到完全相同的结果。
Compiler Explorer demo: standardCompiler Explorer demo: non-standard
优化后的代码相当于
printf("%d\n", 1);
printf("%d\n", 2);
printf("%d\n", 3);
printf("%d\n", 4);
printf("%d\n", 5);
printf("%d\n", 6);
printf("%d\n", 7);
printf("%d\n", 8);
【讨论】:
您没有阻止优化循环使答案偏离主题。 @0___________ 你没看答案吗? 是的,你的例子都没有保留循环。 @0___________,不管你怎么说,你显然没有,因为提到了循环展开的事实。发生这种情况的事实支持了答案提出的观点,您显然也没有阅读。【参考方案2】:您的示例有两个问题:
-
编译器不优化代码。
琐碎
ad 1. 未优化的代码不适合任何性能或输出汇编比较。
ad 2. 代码的琐碎性使您无法启用优化。您需要添加一些内容以防止编译器删除代码。
我会添加一些内存屏障(gcc)
void foo()
for(unsigned i = 0; i<8;)
i++;
asm("":"=r"(i):"m"(i));
void bar()
for(unsigned i = 0; i<8;i++)
asm("":"=r"(i):"m"(i));
生成的代码一模一样
foo:
sub sp, sp, #16
mov w0, 0
.L2:
add w0, w0, 1
str w0, [sp, 12]
cmp w0, 7
bls .L2
add sp, sp, 16
ret
bar:
sub sp, sp, #16
str wzr, [sp, 12]
.L7:
add w0, w0, 1
str w0, [sp, 12]
cmp w0, 7
bls .L7
add sp, sp, 16
ret
https://godbolt.org/z/zTjnjK
【讨论】:
asm("":"=r"(i):"m"(i));
超级怪;它告诉编译器您希望在内存中输入,并且在 asm 之后,i
的值将在寄存器中(使用只写输出操作数)。但是您的 asm 模板不 执行该加载,因此编译器碰巧留在它为"=r"
选择的寄存器中的剩余值仍然是i
只是运气。这可能会完全破坏更复杂的周围代码。
如果你想强制编译器在寄存器中实现一个值而忘记它对这个值的了解,请使用asm volatile("" : "+r"(i))
。 (包括volatile
,因此它无法确定该值最终未被使用并优化整个循环,这在理论上可能在 C++ 中是可能的,其中(与 C 不同)没有副作用的无限循环是 UB。)
@PeterCordes 它与答案有什么关系? (提示:应该表明i++的位置不会影响生成的代码)
有缺陷的内联汇编只是碰巧可以工作,这绝不是一个好例子;人们很难在没有在 SO 答案中找到误导性示例的情况下学习。您将其称为“内存屏障”,但实际上您是在告诉编译器 i
从寄存器中获取一个值,而不强制编译器将 i
的原始值放在同一个寄存器中。将i
存储到内存是一个单独的效果,您可以将asm("" :: "m"(i))
作为单独的asm 语句来获得。
IDK 为什么要强制编译器将i
存储到内存中; asm("" : "+r"(i))
具有良好优化的 do-while 循环结构的预期效果,分支位于底部:godbolt.org/z/Wer9eE,避免任何堆栈操作以腾出空间来溢出本地。以上是关于使用非常规的 for 循环更高效的 Asm? [复制]的主要内容,如果未能解决你的问题,请参考以下文章