当前位置: 首页 > 知识库问答 >
问题:

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

廉实
2023-03-14

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

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

当使用-O3标志的Clang版本6.0.0-1Ubuntu2(Tags/Release_600/Final)编译时,它发出

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

在这里,代码以int*Aint*B别名的大小写两次存储回(%RDI)

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

void adds(int * restrict a, int * restrict b) {
    *a += *b;
    *a += *b;
}
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

共有1个答案

斜宁
2023-03-14

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到1.53-noalias禁用
  • 锈蚀1.53通过???-noalias条件启用

>

  • 当前大小写

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

    其他

    • 使用LLVM的作用域noalias元数据#16515
    • 错过优化:来自指针的引用不被视为noalias#38941
    • NOAlias不够#53105
    • 可变NOAlias:重新永久启用,仅用于panic=abort或stability标志?#45029

  •  类似资料:
    • (这个问题与此密切相关,但它是一个更具体的问题,我希望能就此得到答案)

    • 在我的系统上,行-主顺序访问平均花费(试用),而列-主顺序访问在我的系统上花费(试用),这是相当重要的。 从表面上看,这应该是一件非常简单的事情来优化。 为什么现代编译器不优化这些场景?

    • 奇怪的是,标记为“OK”的行编译得很好,但标记为“Error”的行失败了。它们看起来基本上是一样的。

    • 我有一个简单的测试设置,如 但当我尝试编译测试时,我会遇到53个错误,比如 实际上并没有传达任何关于问题所在的有用信息。我只能假设在我的构建中没有正确配置某些内容。sbt文件或其他地方。 这段代码确实曾经工作过,在我清理东西的过程中,事情发生了变化,现在它被破坏了,没有好的诊断。 有人能提出要找的东西吗?

    • 考虑下面的代码片段 通过-O3优化,最新的gcc和clang版本没有优化指向包装器的指针、指向底层函数的指针。参见第22行的组装: 在后面的+中,编译器将指针放置到,而不只是。 编辑2。同样模式的更简单的例子: gcc 8.2通过抛出指向包装器的指针并将直接存储在其位置(https://gcc.godbolt.org/z/nmibeo)成功地优化了这段代码。然而,按照注释更改代码行(即手动执行相同

    • 我经常注意到gcc在可执行文件中将乘法转换为移位。当将与相乘时,可能会发生类似的情况。例如,可能只是将的指数增加1,从而节省一些周期。如果有人要求编译器这样做(例如,通过),编译器通常会这样做吗? 编译器通常是否足够聪明来执行此操作,还是我需要自己使用或函数系列来执行此操作?