据我所知,引用/指针别名可以阻碍编译器生成优化代码的能力,因为它们必须确保在两个引用/指针确实是别名的情况下生成的二进制代码表现正确。例如,在以下C代码中,
void adds(int *a, int *b) {
*a += *b;
*a += *b;
}
使用clang版本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
在这段代码中,为了防止int *a
和int *b
别名存储,代码将结果两次存储回(%rdi)
。
当我们使用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 确保(除了不安全的代码之外)两个可变引用不能别名,因此我认为编译器应该能够发出更优化的代码版本。
当我使用下面的代码进行测试,并使用 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
仍有可能成为别名?
unsafe
代码中,别名可变引用也是不允许的,并且会导致未定义行为。您可以有别名原始指针,但是unsafe
代码实际上不允许您忽略Rust的标准规则。这只是一个常见的误解,因此值得指出。 - Lukas Kalbertodtadds
函数体中的两个+=
操作是否可以被重新解释为*a = *a + *b + *b
。如果指针不别名,它们可以被重新解释,你甚至可以在第二个汇编列表中看到相当于b* + *b
的内容:2: 01 c0 add %eax,%eax
。但是如果它们别名,则无法这样做,因为当您第二次添加*b
时,它将包含与第一次不同的值(即您在第一个汇编列表的第4行存储的值)。 - dlukes*a += 2 * (*b)
的等效表达式,以供未来读者参考。 - Peter Cordes