rust - 为什么生成的程序集在内联时通过引用返回和复制不等价?

标签 rust

我有一个小结构:

pub struct Foo {
    pub a: i32,
    pub b: i32,
    pub c: i32,
}

我使用的是 (a,b) (b,c) (c,a) 形式的字段对。为了避免代码重复,我创建了一个实用函数,它允许我迭代对:

fn get_foo_ref(&self) -> [(&i32, &i32); 3] {
    [(&self.a, &self.b), (&self.b, &self.c), (&self.c, &self.a)]
}

我必须决定是应该将值作为引用返回还是复制 i32。后来,我打算改用非 Copy 类型而不是 i32,所以我决定使用引用。我预计生成的代码应该是等效的,因为所有内容都将被内联。

我总体上对优化持乐观态度,因此我怀疑与手写代码示例相比,使用此功能时的代码是等效的。

首先是使用函数的变体:

pub fn testing_ref(f: Foo) -> i32 {
    let mut sum = 0;

    for i in 0..3 {
        let (l, r) = f.get_foo_ref()[i];

        sum += *l + *r;
    }

    sum
}

然后是手写变体:

pub fn testing_direct(f: Foo) -> i32 {
    let mut sum = 0;

    sum += f.a + f.b;
    sum += f.b + f.c;
    sum += f.c + f.a;

    sum
}

令我失望的是,所有 3 种方法都产生了不同的汇编代码。最差的代码是针对有引用的情况生成的,而最好的代码是根本没有使用我的实用程序函数的代码。这是为什么?在这种情况下,编译器不应该生成等效代码吗?

您可以查看 resulting assembly code on Godbolt ;我也有“等效”assembly code from C++ .

在 C++ 中,编译器在 get_fooget_foo_ref 之间生成等效代码,尽管我不明白为什么所有 3 种情况的代码都不等效。

为什么编译器没有为所有 3 种情况生成等效代码?

更新:

我稍微修改了代码以使用数组并添加了一个直接案例。
Rust version with f64 and arrays
C++ version with f64 and arrays
这次在C++中生成的代码完全一样。但是 Rust 的程序集不同,通过引用返回会导致更糟糕的程序集。

好吧,我想这是另一个例子,没有什么是理所当然的。

最佳答案

TL;DR:微基准测试是骗人的,指令数不会直接转化为高/低性能。


Later on, I plan to switch to a non-Copy type instead of an i32, so I decided to use references.

然后,您应该检查为您的新类型生成的程序集。

在您的优化示例中,编译器非常狡猾:

pub fn testing_direct(f: Foo) -> i32 {
    let mut sum = 0;

    sum += f.a + f.b;
    sum += f.b + f.c;
    sum += f.c + f.a;

    sum
}

产量:

example::testing_direct:
        push    rbp
        mov     rbp, rsp
        mov     eax, dword ptr [rdi + 4]
        add     eax, dword ptr [rdi]
        add     eax, dword ptr [rdi + 8]
        add     eax, eax
        pop     rbp
        ret

大致是 sum += f.a;总和 += f.b;总和 += f.c; sum += sum;.

也就是说,编译器意识到:

  1. f.X 被添加了两次
  2. f.X * 2 相当于加了两次

虽然前者在其他情况下可以通过使用间接来抑制,但后者非常特定于 i32(并且加法是可交换的)。

例如,将您的代码切换到 f32(仍然是 Copy,但加法不再是可交换的),我为 testing_direct< 得到了完全相同的程序集testing(testing_ref 略有不同):

example::testing:
        push    rbp
        mov     rbp, rsp
        movss   xmm1, dword ptr [rdi]
        movss   xmm2, dword ptr [rdi + 4]
        movss   xmm0, dword ptr [rdi + 8]
        movaps  xmm3, xmm1
        addss   xmm3, xmm2
        xorps   xmm4, xmm4
        addss   xmm4, xmm3
        addss   xmm2, xmm0
        addss   xmm2, xmm4
        addss   xmm0, xmm1
        addss   xmm0, xmm2
        pop     rbp
        ret

再也没有诡计了。

所以从你的例子中真的不可能推断出太多,检查真实类型。

关于rust - 为什么生成的程序集在内联时通过引用返回和复制不等价?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42076205/

相关文章:

rust - 在Rust中的HashMap键上的工程(: Using reference (borrow?)上

rust - 我如何在没有 Rust 编译器的情况下在另一台机器上运行 cargo 测试?

rust - 在结构实现中使用不同的生命周期

generics - 在结构中存储通用闭包

syntax - 为什么 _ 在语句末尾销毁?

rust - 如何实现 future 功能的流

rust - 如何使用自定义 llc 编译 Rust 程序?

3d - 四元数旋转导致场景拉伸(stretch)

rust - Rust 中的 "0is"表示法是什么?

windows - Windows 上的 Rust 回溯?