【问题标题】:Does the Rust compiler automatically remove unnecessary intermediate variables?Rust 编译器会自动删除不必要的中间变量吗?
【发布时间】:2020-07-09 18:38:26
【问题描述】:

在某些语言中,我被告知编译器将执行优化以删除不必要的“中间”局部变量以提高执行效率。

有人知道 Rust 是否也这样做吗?例如,考虑以下代码 sn-ps:

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);
}

这些会生成相同的机器代码吗?

换句话说,编译器是否“意识到”第一个实现中的四个局部变量在给定硬编码整数输入的情况下可证明与第二个实现等效?

【问题讨论】:

  • 从来没有关于优化的保证,但是像这样的小例子很容易优化,所以它尽可能接近保证。建议你直接看机器码,自己对比一下。
  • 感谢 Sven,我没有意识到我可以如此轻松地从操场上看到 ASM 来比较编译器输出。 Cormac 的回答通过他的 Playground 链接清楚地表明了这一点。

标签: performance variables optimization rust compiler-construction


【解决方案1】:

这是测试版本的playground link。查看发布模式下生成的程序集:

playground::main:
    pushq   %r15
    pushq   %r14
    pushq   %r12
    pushq   %rbx
    subq    $72, %rsp
    ####################### f1() here
    movl    $320, 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    $2, 32(%rsp)
    movq    $0, 40(%rsp)
    leaq    8(%rsp), %rbx
    movq    %rbx, 56(%rsp)
    movq    $1, 64(%rsp)
    movq    std::io::stdio::_print@GOTPCREL(%rip), %r12
    leaq    24(%rsp), %rdi
    callq   *%r12
    ####################### f2() here
    movl    $320, 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    $2, 32(%rsp)
    movq    $0, 40(%rsp)
    movq    %rbx, 56(%rsp)
    movq    $1, 64(%rsp)
    leaq    24(%rsp), %rdi
    callq   *%r12
    addq    $72, %rsp
    popq    %rbx
    popq    %r12
    popq    %r14
    popq    %r15
    retq

由于在您的示例中返回值是静态已知的,因此这些函数甚至不会出现在编译后的代码中。即使您像这样定义函数,您实际上也会得到相同的结果:

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    $1, %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    $2, 32(%rsp)
    movq    $0, 40(%rsp)
    leaq    8(%rsp), %rbp
    movq    %rbp, 56(%rsp)
    movq    $1, 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

编译器实际上已经认识到这些函数是相同的,并将它们折叠成一个定义。

【讨论】:

    猜你喜欢
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 2021-03-13
    • 2014-12-03
    • 1970-01-01
    • 1970-01-01
    • 2011-09-09
    • 1970-01-01
    相关资源
    最近更新 更多