GCC vs CLANG 指向 char* 优化的指针
Posted
技术标签:
【中文标题】GCC vs CLANG 指向 char* 优化的指针【英文标题】:GCC vs CLANG pointer to char* optimization 【发布时间】:2017-09-09 09:15:16 【问题描述】:我有这个代码: 主PC:
int main(int c, char **v)
char *s = v[0];
while (*s++ != 0)
if ((*s == 'a') && (*s != 'b'))
return 1;
return 0;
我用 clang 和 gcc 编译生成汇编代码来比较优化:
clang-3.9 -S -masm=intel -O3 mainP.c
gcc -S -masm=intel -O3 mainP.c
编译器版本为:
clang version 3.9.1-9 (tags/RELEASE_391/rc2)
Target: x86_64-pc-linux-gnu
gcc (Debian 6.3.0-18) 6.3.0 20170516
生成的 2 个汇编代码是:
gcc 汇编代码:
main:
.LFB0:
.cfi_startproc
mov rax, QWORD PTR [rsi]
jmp .L2
.p2align 4,,10
.p2align 3
.L4:
cmp BYTE PTR [rax], 97
je .L5
.L2:
add rax, 1
cmp BYTE PTR -1[rax], 0
jne .L4
xor eax, eax
ret
.L5:
mov eax, 1
ret
clang 汇编代码:
main: # @main
.cfi_startproc
# BB#0:
mov rcx, qword ptr [rsi]
mov dl, byte ptr [rcx]
inc rcx
.p2align 4, 0x90
.LBB0_1: # =>This Inner Loop Header: Depth=1
xor eax, eax
test dl, dl
je .LBB0_3
# BB#2: # in Loop: Header=BB0_1 Depth=1
movzx edx, byte ptr [rcx]
inc rcx
mov eax, 1
cmp dl, 97
jne .LBB0_1
.LBB0_3:
ret
我注意到:在 gcc 汇编代码中,*s 在循环中被访问两次,而 *s 在 clang 汇编代码中只被访问一次。
有什么区别的解释吗?
然后在稍微更改 C 代码(添加一个本地 char 变量)后,我得到的汇编代码与 GCC 大致相同:
int main(int c, char **v)
char *s = v[0];
char ch;
ch = *s;
while (ch != 0)
if ((ch == 'a') && (ch != 'b'))
return 1;
ch = *s++;
return 0;
使用 GCC 生成的汇编代码:
main:
.LFB0:
.cfi_startproc
mov rax, QWORD PTR [rsi]
movzx edx, BYTE PTR [rax]
test dl, dl
je .L6
add rax, 1
cmp dl, 97
jne .L5
jmp .L8
.p2align 4,,10
.p2align 3
.L4:
cmp dl, 97
je .L8
.L5:
add rax, 1
movzx edx, BYTE PTR -1[rax]
test dl, dl
jne .L4
.L6:
xor eax, eax
ret
.L8:
mov eax, 1
ret
【问题讨论】:
s++
出现在两个 C++ 代码片段的不同位置。
clang 只需省略第二次读取。无论这好坏,都应该对代码进行更仔细的分析。你是在问谁表现更好或更玄学为什么?
我不是在问哪个更好。这取决于情况,如果 gcc 代码中两次读取之间的内存发生变化,则与运行 clang 代码相比,行为会有所不同。
第二次读取是*s++
,困难在于它并不总是第二次读取(第一次迭代)。编译器需要将 *(c?a:b) 转换为 c?*a:*b 并注意 *b 在采用该分支时是多余的,在这种情况下 gcc 会丢失。随时向 gcc 的 bugzilla 报告这个错过的优化。
关于 GCC 的错误报告链接:gcc.gnu.org/bugzilla/show_bug.cgi?id=82187
【参考方案1】:
差异的解释:编译器优化是困难,不同的编译器会以不同的方式优化你的代码。
因为表达式比较简单,我们可以假设*s
在两个地方都是相同的值,只需要一个负载。
但是弄清楚优化是棘手的,因为编译器必须绝对“知道”在第一个引用和第二个引用之间不能更改“任何 s 指向的内容”。
在你的例子中,clang 比 gcc 更擅长解决这个问题。
【讨论】:
gcc 是否有一个选项,以便它从第一个 c 代码生成汇编代码,只有一次访问,如 clang?此外,生成的汇编代码在使用不同的编译器版本编译时肯定会发生变化。跨度> 我不知道是否有编译器选项,而且版本之间存在差异我并不感到惊讶——每个版本几乎总是对优化器进行改进/更改。 如果您想强制进行一次访问,我建议您在 C 代码中使用中间变量,就像您在第二个示例中所做的那样。 并不是 gcc 认为它没有意识到它是同一个位置并且它可以假设它没有被修改,而是它没有将循环管道/转换为特殊情况下的零长度字符串,然后能够在源循环的下一次迭代中为!=0
重用加载的字符。 ***.com/questions/46129083/…。 gcc/clang 也不会像这样在第一次迭代之前无法确定迭代计数的自动矢量化(使用 SSE2)搜索循环,所以我认为条件 return
是有问题的。
其实问题不在于条件返回。计算整个字符串的匹配可以避免这种情况,但它仍然是一个未知的迭代计数,并且 gcc 仍然会加载两次 godbolt.org/g/vnshJL。以上是关于GCC vs CLANG 指向 char* 优化的指针的主要内容,如果未能解决你的问题,请参考以下文章
为啥这个结构文字在 VS2013 中通过地址而不是 gcc/clang 时会损坏?
Clang vs GCC - 产生更快的二进制文件? [关闭]
从通用 lambda 调用 `this` 成员函数 - clang vs gcc