假定两个可变引用不能别名,为什么 Rust 编译器不优化代码?
Why does the Rust compiler not optimize code assuming that two mutable references cannot alias?
据我所知,reference/pointer 别名会阻碍编译器生成优化代码的能力,因为它们必须确保生成的二进制文件在两个 references/pointers 确实是别名的情况下正确运行。例如,在下面的 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 # 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 *a
和 int *b
别名。
当我们明确告诉编译器这两个指针不能与restrict
关键字进行别名时:
void adds(int * restrict a, int * restrict b) {
*a += *b;
*a += *b;
}
然后 Clang 将发出一个更优化的版本,该版本有效地执行 *a += 2 * (*b)
,如果(如 restrict
所承诺的)*b
未通过分配给 [=] 进行修改,则等效25=]:
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 确保(不安全代码除外)两个可变引用不能别名,我认为编译器应该能够发出更优化的代码版本。
当我使用下面的代码进行测试并使用 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 中,a
和 b
仍然有可能混叠吗?
Rust 最初 确实 启用了 LLVM 的 noalias
属性,但是这个 caused miscompiled code. When all supported LLVM versions no longer miscompile the code, it will be re-enabled.
如果您将 -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 版本有条件地启用
相关的 Rust 问题
当前案例
之前的案例
其他
据我所知,reference/pointer 别名会阻碍编译器生成优化代码的能力,因为它们必须确保生成的二进制文件在两个 references/pointers 确实是别名的情况下正确运行。例如,在下面的 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 # 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 *a
和 int *b
别名。
当我们明确告诉编译器这两个指针不能与restrict
关键字进行别名时:
void adds(int * restrict a, int * restrict b) {
*a += *b;
*a += *b;
}
然后 Clang 将发出一个更优化的版本,该版本有效地执行 *a += 2 * (*b)
,如果(如 restrict
所承诺的)*b
未通过分配给 [=] 进行修改,则等效25=]:
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 确保(不安全代码除外)两个可变引用不能别名,我认为编译器应该能够发出更优化的代码版本。
当我使用下面的代码进行测试并使用 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 中,a
和 b
仍然有可能混叠吗?
Rust 最初 确实 启用了 LLVM 的 noalias
属性,但是这个 caused miscompiled code. When all supported LLVM versions no longer miscompile the code, it will be re-enabled.
如果您将 -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 版本有条件地启用
相关的 Rust 问题
当前案例
之前的案例
其他