假设两个可变引用都不能别名,Rust编译器为什么不优化代码?


301

据我所知,引用/指针别名会阻碍编译器生成优化代码的能力,因为它们必须确保在两个引用/指针确实是别名的情况下,生成的二进制文件的行为正确。例如,在以下C代码中,

void adds(int  *a, int *b) {
    *a += *b;
    *a += *b;
}

clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)-O3标志编译时,它发出

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)  # The first time
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)  # The second time
   a:    c3                       retq

下面的代码存回(%rdi)两次的情况下,int *aint *b别名。

当我们明确告诉编译器这两个指针不能使用restrict关键字别名时:

void adds(int * restrict a, int * restrict b) {
    *a += *b;
    *a += *b;
}

然后Clang将发出二进制代码的更优化版本:

0000000000000000 <adds>:
   0:    8b 06                    mov    (%rsi),%eax
   2:    01 c0                    add    %eax,%eax
   4:    01 07                    add    %eax,(%rdi)
   6:    c3                       retq

由于Rust确保(不安全代码中除外)两个可变引用不能别名,因此我认为编译器应该能够发出代码的更优化版本。

当我用下面的代码测试,并编译它rustc 1.35.0-C opt-level=3 --emit obj

#![crate_type = "staticlib"]
#[no_mangle]
fn adds(a: &mut i32, b: &mut i32) {
    *a += *b;
    *a += *b;
}

它产生:

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)
   a:    c3                       retq

这不采取保证的优势a,并b不能别名。

这是因为当前的Rust编译器仍在开发中,并且尚未合并别名分析来进行优化吗?

这是因为即使在安全的Rust中,仍然有可能ab可能出现别名吗?



76
旁注:“ 由于Rust确保(不安全代码中除外)两个可变引用不能别名 ” –值得一提的是,即使在unsafe代码中,也不允许使用别名可变引用并导致未定义行为。您可以使用别名原始指针,但是unsafe代码实际上不允许您忽略Rust标准规则。这只是一个普遍的误解,因此值得指出。
卢卡斯·卡尔伯托德

6
我花了一些时间弄清楚示例的内容,因为我不懂asm,所以万一它对其他人有帮助:归结为是否可以将+=正文中的两个操作adds重新解释为*a = *a + *b + *b。如果指针没有别名,它们可以别名,您甚至可以b* + *b在第二个asm清单中查看内容2: 01 c0 add %eax,%eax。但是,如果它们使用别名,则它们不能这样做,因为*b到第二次添加时,它将包含与第一次(4:在第一个asm列表的行中存储的值)不同的值。
dlukes

Answers:


364

Rust最初确实启用了LLVM的noalias属性,但这导致了错误的代码编译。当所有受支持的LLVM版本不再重新编译代码时,它将重新启用

如果添加-Zmutable-noalias=yes到编译器选项,则会得到预期的程序集:

adds:
        mov     eax, dword ptr [rsi]
        add     eax, eax
        add     dword ptr [rdi], eax
        ret

简而言之,Rust在所有地方都使用了C的restrict关键字,比任何普通的C程序都流行得多。这对LLVM的极端情况产生了超出其正确处理能力的影响。事实证明,C和C ++程序员根本不像Rust中那样频繁使用。restrict&mut

这已经发生过多次了

  • Rust 1.0至1.7-已noalias启用
  • Rust 1.8到1.27- noalias禁用
  • Rust 1.28至1.29- noalias启用
  • Rust 1.30到??? — noalias禁用

相关Rust问题


12
这不足为奇。尽管LLVM拥有广泛的多语言友好性主张,但它是专门为C ++后端而设计的,并且一直很容易在看起来不像C ++的事物上窒息。
梅森惠勒

47
@MasonWheeler如果单击某些问题,则可以找到restrict在Clang和GCC上使用和错误编译的C代码示例。不仅限于“ C ++不够”的语言,除非您将C ++本身归入该组
Shepmaster

6
@MasonWheeler:我认为LLVM并不是真正围绕C或C ++规则设计的,而是围绕LLVM规则设计的。它做出的假设通常适用于C或C ++代码,但是据我所知,该设计基于静态数据依赖模型,该模型无法处理棘手的极端情况。如果它悲观地假设无法证明数据依赖关系,那将是可以的,但是它将其视为无操作操作,这些操作将以与其持有的位模式相同的位模式写入存储,并且在存储操作上具有潜在但不可证明的数据依赖关系。读和写。
超级猫

8
@supercat我已经读过几次您的评论,但是我承认我很困惑—我不知道他们与这个问题或答案有什么关系。未定义的行为在这里不起作用,这只是“多次”优化传递彼此之间不良交互的情况。
Shepmaster

2
@avl_sweden重申一下,这只是一个错误。循环展开优化步骤noalias在执行时并没有完全考虑指针。它根据输入指针创建了新的指针,noalias即使新的指针具有别名,也会不正确地复制属性。
Shepmaster
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.