为啥clang忽略__restrict__?

Posted

技术标签:

【中文标题】为啥clang忽略__restrict__?【英文标题】:Why does clang ignore __restrict__?为什么clang忽略__restrict__? 【发布时间】:2018-05-16 07:48:45 【问题描述】:

我刚刚测试了一个小例子来检查__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 根本不支持它同时支持和忽略关键字本身。那么,这里的问题是什么?我做错什么了吗?

【问题讨论】:

可能是因为__restrict__ 从未在 C++ 标准中定义,只是一个 gcc 扩展? 它不仅是一个 GCC 扩展,它还是一个可以安全忽略的扩展。它只影响效率,不影响正确性。 @Bob__:我做到了。不是这个例子,而是来自我们真实代码的例子。这就是为什么我首先在​​玩__restrict__。我没有因为过早的优化而得到报酬;)。我们的热循环非常紧凑,额外的内存负载使我们的性能下降。 @MSalters:我知道。当然可以忽略。但它仍然非常可行。仅仅因为某些 can 被忽略并不意味着现代编译器会这样做。在优化潜力方面,clang 通常在大多数编译器之上。我只是觉得奇怪,他们会不会忽略这种情况下的潜力。 @MSalters:当然可以。在这个最小的例子中。但是,如果您在一个传递给热循环的 lambda 中并且捕获了 v2 怎么办?然后你不能在循环之前把它放到堆栈上,因为你(即 lambda)不控制循环。 【参考方案1】:

这么多没用的cmets...

这似乎是 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)。

【讨论】:

以上是关于为啥clang忽略__restrict__?的主要内容,如果未能解决你的问题,请参考以下文章

clang-format 如何忽略外部 C?

为啥clang++更喜欢adcx而不是adc

__RESTRICT修改为__RRSTRICT,程序闪退。

为啥在 gcc 和 clang 上通过优化将大 double 转换为 uint16_t 会给出不同的答案

为啥我收到错误“/bin/sh: x86_64-apple-darwin13.4.0-clang: command not found”?

为啥 Git 忽略不适用于 __pycache__ 文件夹? [复制]