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



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

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

当由带有-O3标志的clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)编译时,它会发出

0000000000000000 <adds>:
0:    8b 07                    mov    (%rdi),%eax  # load a into EAX
2:    03 06                    add    (%rsi),%eax  # load-and-add b
4:    89 07                    mov    %eax,(%rdi)  # store into a
6:    03 06                    add    (%rsi),%eax  # load-and-add b again
8:    89 07                    mov    %eax,(%rdi)  # store into a again
a:    c3                       retq

在这里,代码存储回(%rdi)两次,以防万一int *aint *b别名。

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

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

然后 Clang 将发出一个更优化的版本,有效地执行*a += 2 * (*b),如果(如restrict所承诺的那样)*b没有通过分配给*a进行修改,则等效

0000000000000000 <adds>:
0:    8b 06                    mov    (%rsi),%eax   # load b once
2:    01 c0                    add    %eax,%eax     # double it
4:    01 07                    add    %eax,(%rdi)   # *a += 2 * (*b)
6:    c3                       retq

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

当我使用以下代码进行测试并使用-C opt-level=3 --emit objrustc 1.35.0编译它时,

#![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

这不会利用ab不能别名的保证。

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

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

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 中使用&mut那样频繁地使用restrict

这种情况已经发生过多次

  • Rust 1.0 到 1.7 — 启用noalias
  • Rust 1.8 到 1.27 —noalias禁用
  • Rust 1.28 到 1.29 — 启用noalias
  • Rust 1.30 到 1.54 —noalias禁用
  • Rust 1.54 到 ??? —noalias有条件地启用,具体取决于编译器使用的 LLVM 版本

相关锈问题

  • 当前案例

    • 代数矩阵的代码生成不正确::swap_rows() #54462
    • 一旦 LLVM 不再错误编译它们,默认情况下重新启用 noalias 注释 #54878
    • 为 LLVM 启用可变 noalias>= 12 #82834
    • 回归:由于"可变 noalias"逻辑中的错误而导致的错误编译 #84958
  • 以前的案例

    • 通过不将 &mut 指针标记为 noalias 来解决 LLVM 优化器错误 #31545
    • 一旦 LLVM 不再错误编译它们,就将指针标记为 noalias #31681
  • 其他

    • 利用 LLVM 的作用域 noalias 元数据 #16515
    • 错过的优化:来自指针的引用不被视为 noalias #38941
    • noalias 还不够 #53105
    • 可变 noalias:永久重新启用,仅适用于 panic=中止,还是稳定标志? #45029

相关内容

  • 没有找到相关文章

最新更新