为什么生成的程序集在内联返回和内联复制之间不相等?

Ale*_*lar 6 rust

我有一个小结构:

pub struct Foo {
    pub a: i32,
    pub b: i32,
    pub c: i32,
}
Run Code Online (Sandbox Code Playgroud)

我在表单中使用了一对字段(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)]
}
Run Code Online (Sandbox Code Playgroud)

我必须决定是否应该将值作为引用返回或复制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
}
Run Code Online (Sandbox Code Playgroud)

然后是手写的变体:

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
}
Run Code Online (Sandbox Code Playgroud)

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

您可以在Godbolt上查看生成的汇编代码 ; 我也有来自C++的'等效' 汇编代码.

在C++中,编译器在get_foo和之间生成了等效代码get_foo_ref,虽然我不明白为什么所有3种情况的代码都不相等.

为什么编译器没有为所有3个案例生成等效代码?

更新:

我稍微修改了代码以使用数组并添加一个直接案例.
带有f64的Rust版本和带有f64和数组的数组
C++版本
这次在C++中生成的代码完全相同.然而,Rust'组件不同,并且通过引用返回导致更糟糕的组装.

好吧,我想这是另一个例子,没有什么可以被视为理所当然.

Mat*_* M. 2

TL;DR:微基准测试是有技巧的,指令数并不能直接转化为高/低性能。


后来,我打算切换到非复制类型而不是 i32,所以我决定使用引用。

然后,您应该检查生成的程序集是否符合您的新类型。

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

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
}
Run Code Online (Sandbox Code Playgroud)

产量:

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
Run Code Online (Sandbox Code Playgroud)

大概是这样sum += f.a; sum += f.b; sum += 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
Run Code Online (Sandbox Code Playgroud)

而且不再有任何诡计了。

因此,实际上不可能从您的示例中推断出太多内容,请检查真实类型。