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

时间:2017-02-06 19:57:53

标签: 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'装配不同,通过引用返回导致更糟糕的装配。

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

1 个答案:

答案 0 :(得分: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
}

收率:

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; sum += f.b; sum += f.c; sum += sum;

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

  1. f.X已添加两次
  2. f.X * 2相当于将其添加两次
  3. 虽然在其他情况下可以通过使用间接禁止前者,但后者非常特定于i32(并且添加是可交换的)。

    例如,将代码切换为f32(仍为Copy,但添加不再是可交换的),我为testing_directtesting获得了相同的程序集(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
    

    再也没有诡计了。

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