【问题标题】:What is the preferred way to implement the Add trait efficiently for Vector type为 Vector 类型有效实现 Add 特征的首选方法是什么
【发布时间】:2015-02-02 17:47:58
【问题描述】:

Add 特征定义为seen in the documentation

为 Vector 实现它时,需要将其复制到 add 方法中以允许像 v1 + v2 这样的语法。如果 add 实现更改为支持借用引用并因此防止复制,则必须编写 &v1 + &v2,这是不可取的。

首选或最佳执行方式是什么?

(在 C++ 中,self 将是 const Vector<T>&,以及 rhs,但仍允许所需的 v1 + v2 语义。)

代码

为了完整起见,摘录我现在使用的代码

use std::num::Float;
use std::ops::Add;

#[derive(Debug, PartialEq, Eq, Copy)]
pub struct Vector<T: Float> {
    x: T,
    y: T,
    z: T,
}

impl<T: Float> Add for Vector<T> {
    type Output = Vector<T>;

    // Probably it will be optimized to not actually copy self and rhs for each call !
    #[inline(always)]
    fn add(self, rhs: Vector<T>) -> Vector<T> {
      Vector {  x: self.x + rhs.x,
                y: self.y + rhs.y, 
                z: self.z + rhs.z }
    }
}


#[cfg(test)]
#[test]
fn basics() {
    let v32 = Vector { x: 5.0f32, y: 4.0f32, z: 0.0f32 };
    let v32_2 = v32 + v32;
    assert_eq!(v32_2.x, v32.x + v32.x);
    assert_eq!(v32_2.y, v32.y + v32.y);
    assert_eq!(v32_2.z, v32.z + v32.z);
}

【问题讨论】:

  • 我个人认为编译器在使用“拥有”值时应该能够做得更好,因为它可以随意移动它们。我通常担心会创建不必要的副本,但在这方面可能会有所不同。

标签: rust


【解决方案1】:

由于您的 Vector 仅包含三个实现 Float 特征的值(这意味着它们是 f64f32),除非您已经分析了您的程序并确定了它们,否则您不应该真正担心它们被复制多个副本会导致性能下降。

如果您的类型不可复制并且在构造时需要分​​配(例如大整数和大浮点数),您可以实现按值和按引用调用的所有可能组合:

impl Add<YourType> for YourType { ... }
impl<'r> Add<YourType> for &'r YourType { ... }
impl<'a> Add<&'a YourType> for YourType { ... }
impl<'r, 'a> Add<&'a YourType> for &'r YourType { ... }

并在接受至少一个按值参数的实现中重用分配的存储。但是,在这种情况下,如果您不想将值移动到调用中,需要使用 &amp; 运算符。 Rust 更喜欢显式而不是隐式;如果你需要引用语义,你必须明确地写出来。

FWIW,你可以看看this 程序,尤其是它的汇编输出。我相信这个组件负责所有的算术运算:

shrq    $11, %r14
cvtsi2sdq   %r14, %xmm0
mulsd   .LCPI0_0(%rip), %xmm0
shrq    $11, %r15
cvtsi2sdq   %r15, %xmm1
mulsd   .LCPI0_0(%rip), %xmm1
shrq    $11, %rbx
cvtsi2sdq   %rbx, %xmm2
mulsd   .LCPI0_0(%rip), %xmm2
movaps  %xmm0, %xmm3
addsd   %xmm1, %xmm3
movaps  %xmm1, %xmm4
addsd   %xmm2, %xmm4
movaps  %xmm0, %xmm5
addsd   %xmm2, %xmm5
addsd   %xmm2, %xmm3
addsd   %xmm0, %xmm4
addsd   %xmm1, %xmm5
movsd   %xmm3, 24(%rsp)
movsd   %xmm4, 32(%rsp)
movsd   %xmm5, 40(%rsp)
leaq    (%rsp), %rdi
leaq    24(%rsp), %rsi
callq   _ZN13Vec3$LT$T$GT$9to_string20h7039822990634233867E

对我来说看起来很整洁 - 编译器很好地内联了所有操作。

【讨论】:

  • 我认为,Rust 类型包括它们的“修饰符”,例如 mut&amp;Add被定义为pub trait Add&lt;RHS = Self&gt; ,所以我假设Self的类型在所有方面都必须始终与RHS的类型相同。也许它也与生命周期有关...... .
  • SomeType&lt;T=U&gt; 只是定义了一个默认类型参数,即如果不指定,则假定为this。这允许编写例如T: Add 转换为 T: Add&lt;T&gt;
  • 谢谢!我现在有很多灵光乍现的时刻,我(尝试)学习生锈的来龙去脉!就在几分钟前,我看到了这样的代码,并正确地认为它是默认类型。就我而言,由于某些原因,我感到困惑。将不得不收工,因为事情不再好转了;)
  • 这个答案在Trace Quest 5: Episode 03中引用。
猜你喜欢
  • 2019-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-30
相关资源
最近更新 更多