Rust 编译器会自动删除不必要的中间变量吗?
Does the Rust compiler automatically remove unnecessary intermediate variables?
有人告诉我在某些语言中,编译器将执行优化以删除不必要的“中间”局部变量以提高执行效率。
有人知道 Rust 是否也这样做吗?例如,考虑以下代码片段:
fn main() {
// has four local variables
let x = 3;
let y = 5;
let temp_result = x + y;
let final_result = temp_result * 40;
println!("The final result is: {}", final_result);
}
与下面的实现相比,它似乎有零个显式创建的局部变量
fn main() {
// has no explicitly created local variables
println!("The final result is: {}", (3+5) * 40);
}
这些会生成相同的机器代码吗?
换句话说,如果给定硬编码整数输入,编译器是否“意识到”第一个实现中的四个局部变量可证明等同于第二个实现?
这里是 playground link 测试版。查看发布模式下生成的程序集:
playground::main:
pushq %r15
pushq %r14
pushq %r12
pushq %rbx
subq , %rsp
####################### f1() here
movl 0, 4(%rsp) # whole function optimized to static value of 320
#######################
leaq 4(%rsp), %r14
movq %r14, 8(%rsp)
movq core::fmt::num::imp::<impl core::fmt::Display for i32>::fmt@GOTPCREL(%rip), %r15
movq %r15, 16(%rsp)
leaq .L__unnamed_2(%rip), %rax
movq %rax, 24(%rsp)
movq , 32(%rsp)
movq [=10=], 40(%rsp)
leaq 8(%rsp), %rbx
movq %rbx, 56(%rsp)
movq , 64(%rsp)
movq std::io::stdio::_print@GOTPCREL(%rip), %r12
leaq 24(%rsp), %rdi
callq *%r12
####################### f2() here
movl 0, 4(%rsp) # same as with f1()
#######################
movq %r14, 8(%rsp)
movq %r15, 16(%rsp)
leaq .L__unnamed_3(%rip), %rax
movq %rax, 24(%rsp)
movq , 32(%rsp)
movq [=10=], 40(%rsp)
movq %rbx, 56(%rsp)
movq , 64(%rsp)
leaq 24(%rsp), %rdi
callq *%r12
addq , %rsp
popq %rbx
popq %r12
popq %r14
popq %r15
retq
因为 return 值在您的示例中是静态已知的,所以这些函数甚至不会出现在编译代码中。即使您定义这样的函数,您实际上也会得到同样的结果:
fn f1(a: i32, b: i32, c:i32) -> i32 {
let x = a;
let y = b;
let temp_result = x + y;
let final_result = temp_result * c;
final_result
}
fn f2(a: i32, b: i32, c: i32) -> i32 {
(a + b) * c
}
pub fn main() {
println!("f1() = {}", f1(3, 5, 40));
println!("f2() = {}", f2(3, 5, 40));
}
如果参数在编译时未知会怎样? Here's another playground,这次使用随机计算的值,并且两个函数都标记为 #[inline(never)]
:
playground::f1:
leal (%rdi,%rsi), %eax
imull %edx, %eax
retq
playground::main:
# rng initialization...
.LBB7_20:
movl 8(%rbp,%rax,4), %ebx
addq , %rax
movq %rax, (%rbp)
movl %r15d, %edi
movl %r12d, %esi
movl %ebx, %edx
######################## f1() called here
callq playground::f1 #
########################
movl %eax, 4(%rsp)
leaq 4(%rsp), %rax
movq %rax, 8(%rsp)
movq core::fmt::num::imp::<impl core::fmt::Display for i32>::fmt@GOTPCREL(%rip), %r13
movq %r13, 16(%rsp)
leaq .L__unnamed_3(%rip), %rax
movq %rax, 24(%rsp)
movq , 32(%rsp)
movq [=12=], 40(%rsp)
leaq 8(%rsp), %rbp
movq %rbp, 56(%rsp)
movq , 64(%rsp)
movq std::io::stdio::_print@GOTPCREL(%rip), %r14
leaq 24(%rsp), %rdi
callq *%r14
movl %r15d, %edi
movl %r12d, %esi
movl %ebx, %edx
######################## and again here!
callq playground::f1 #
########################
# ...
retq
编译器实际上已经认识到函数是相同的,并将它们折叠成一个定义。
有人告诉我在某些语言中,编译器将执行优化以删除不必要的“中间”局部变量以提高执行效率。
有人知道 Rust 是否也这样做吗?例如,考虑以下代码片段:
fn main() {
// has four local variables
let x = 3;
let y = 5;
let temp_result = x + y;
let final_result = temp_result * 40;
println!("The final result is: {}", final_result);
}
与下面的实现相比,它似乎有零个显式创建的局部变量
fn main() {
// has no explicitly created local variables
println!("The final result is: {}", (3+5) * 40);
}
这些会生成相同的机器代码吗?
换句话说,如果给定硬编码整数输入,编译器是否“意识到”第一个实现中的四个局部变量可证明等同于第二个实现?
这里是 playground link 测试版。查看发布模式下生成的程序集:
playground::main:
pushq %r15
pushq %r14
pushq %r12
pushq %rbx
subq , %rsp
####################### f1() here
movl 0, 4(%rsp) # whole function optimized to static value of 320
#######################
leaq 4(%rsp), %r14
movq %r14, 8(%rsp)
movq core::fmt::num::imp::<impl core::fmt::Display for i32>::fmt@GOTPCREL(%rip), %r15
movq %r15, 16(%rsp)
leaq .L__unnamed_2(%rip), %rax
movq %rax, 24(%rsp)
movq , 32(%rsp)
movq [=10=], 40(%rsp)
leaq 8(%rsp), %rbx
movq %rbx, 56(%rsp)
movq , 64(%rsp)
movq std::io::stdio::_print@GOTPCREL(%rip), %r12
leaq 24(%rsp), %rdi
callq *%r12
####################### f2() here
movl 0, 4(%rsp) # same as with f1()
#######################
movq %r14, 8(%rsp)
movq %r15, 16(%rsp)
leaq .L__unnamed_3(%rip), %rax
movq %rax, 24(%rsp)
movq , 32(%rsp)
movq [=10=], 40(%rsp)
movq %rbx, 56(%rsp)
movq , 64(%rsp)
leaq 24(%rsp), %rdi
callq *%r12
addq , %rsp
popq %rbx
popq %r12
popq %r14
popq %r15
retq
因为 return 值在您的示例中是静态已知的,所以这些函数甚至不会出现在编译代码中。即使您定义这样的函数,您实际上也会得到同样的结果:
fn f1(a: i32, b: i32, c:i32) -> i32 {
let x = a;
let y = b;
let temp_result = x + y;
let final_result = temp_result * c;
final_result
}
fn f2(a: i32, b: i32, c: i32) -> i32 {
(a + b) * c
}
pub fn main() {
println!("f1() = {}", f1(3, 5, 40));
println!("f2() = {}", f2(3, 5, 40));
}
如果参数在编译时未知会怎样? Here's another playground,这次使用随机计算的值,并且两个函数都标记为 #[inline(never)]
:
playground::f1:
leal (%rdi,%rsi), %eax
imull %edx, %eax
retq
playground::main:
# rng initialization...
.LBB7_20:
movl 8(%rbp,%rax,4), %ebx
addq , %rax
movq %rax, (%rbp)
movl %r15d, %edi
movl %r12d, %esi
movl %ebx, %edx
######################## f1() called here
callq playground::f1 #
########################
movl %eax, 4(%rsp)
leaq 4(%rsp), %rax
movq %rax, 8(%rsp)
movq core::fmt::num::imp::<impl core::fmt::Display for i32>::fmt@GOTPCREL(%rip), %r13
movq %r13, 16(%rsp)
leaq .L__unnamed_3(%rip), %rax
movq %rax, 24(%rsp)
movq , 32(%rsp)
movq [=12=], 40(%rsp)
leaq 8(%rsp), %rbp
movq %rbp, 56(%rsp)
movq , 64(%rsp)
movq std::io::stdio::_print@GOTPCREL(%rip), %r14
leaq 24(%rsp), %rdi
callq *%r14
movl %r15d, %edi
movl %r12d, %esi
movl %ebx, %edx
######################## and again here!
callq playground::f1 #
########################
# ...
retq
编译器实际上已经认识到函数是相同的,并将它们折叠成一个定义。