我刚测试了一个小例子来检查__restrict__
是否在最新的编译器上用C ++工作:
void foo(int x,int* __restrict__ ptr1, int& v2) {
for(int i=0;i<x;i++) {
if(*ptr1==v2) {
++ptr1;
} else {
*ptr1=*ptr1+1;
}
}
}
当使用最新的gcc(gcc8.1 -O3 -std = c ++ 14)在godbolt.org上尝试时,__restrict__
按预期工作:v2
仅加载一次,因为它不能与ptr1
别名。
以下是相关的装配部件:
.L5:
mov eax, DWORD PTR [rsi]
cmp eax, ecx # <-- ecx contains v2, no load from memory
jne .L3
add edx, 1
add rsi, 4
cmp edi, edx
jne .L5
现在与最新的clang相同(clang 6.0.0 -O3 -std = c ++ 14)。它将循环展开一次,因此生成的代码要大得多,但这里有要点:
.LBB0_3: # =>This Inner Loop Header: Depth=1
mov edi, dword ptr [rsi]
cmp edi, dword ptr [rdx] # <-- restrict didn't work, v2 loaded from memory in hot loop
jne .LBB0_9
add rsi, 4
mov edi, dword ptr [rsi]
cmp edi, dword ptr [rdx] # <-- restrict didn't work, v2 loaded from memory in hot loop
je .LBB0_12
为什么会这样?我知道__restrict__
是非标准的,并且编译器可以自由地忽略它,但它似乎是一种非常基本的技术,可以从一些代码中获取最后一点性能,所以我怀疑clang根本就没有在支持和忽略关键字本身的同时支持它。那么,这里的问题是什么?我做错了吗?
答案 0 :(得分:3)
太多无用的注释...
这似乎是Clang别名分析器中的错误。如果您将v2
的类型更改为short
,编译器会根据基于类型的别名规则将其从循环中愉快地删除:
for.body: ; preds = %for.inc, %for.body.lr.ph
%i.09 = phi i32 [ 0, %for.body.lr.ph ], [ %inc, %for.inc ]
%ptr1.addr.08 = phi i32* [ %ptr1, %for.body.lr.ph ], [ %ptr1.addr.1, %for.inc ]
%1 = load i32, i32* %ptr1.addr.08, align 4, !tbaa !5
%cmp1 = icmp eq i32 %1, %conv
br i1 %cmp1, label %if.then, label %if.else
但是在原始循环中,您为两个内存引用都设置了相同的别名集,这就是为什么中端无法对其进行优化的原因:
%i.08 = phi i32 [ %inc, %for.inc ], [ 0, %for.body.preheader ]
%ptr1.addr.07 = phi i32* [ %ptr1.addr.1, %for.inc ], [ %ptr1, %for.body.preheader ]
%0 = load i32, i32* %ptr1.addr.07, align 4, !tbaa !1
%1 = load i32, i32* %v2, align 4, !tbaa !1
%cmp1 = icmp eq i32 %0, %1
br i1 %cmp1, label %if.then, label %if.else
请注意,这两个内存引用都附加了!tbaa !1
,这意味着编译器无法区分它们两个访问的内存。似乎restrict
注释在此过程中丢失了。
我鼓励您使用最新的Clang重现此代码,并在LLVM Bugzilla中提交错误(请确保抄送抄送Hal Finkel)。