< 返回版块

eweca 发表于 2021-01-01 21:25

求教一个关于运算符重载和GC的问题!

如下代码中,我尝试重构Matrix之间和其与f64的所有矩阵运算法则。我想求问一下,所有步骤我都返回一个新的Matrix算是一个比较适合的方法吗?比如let ci = &a + c1 * c2 * &b;相当于let ci = &Matrix1 + Matrix2,在我这个代码里,Matrix2不会被消耗掉,但也不与任何变量绑定,它会在什么时候drop?是赋值给ci的瞬间,它就被drop了吗?还是在main函数结束时?

第二种办法是只允许&Matrix参与运算,得到的答案也是&Matrix。此时,按我的理解是,内存里有一个Matrix但不与任何变量绑定,而我们手头上只能存在有它的引用&Matrix。是这样吗?

哪种办法是比较合理,或者说,计算效率最高的呢?我的理解是计算效率是一致的。但是第一种方法,是否会消耗额外的内存。而第二种方法,写起来方便(只用写关于&Matrix那一套),但手上只有&Matrix引用会比较不方便吗?

此外,f64 + MatrixMatrix + f64需要写两套,很不方便。比如这里我就忘了写Matrix + &Matrix。我看ndarray貌似是用宏解决这个问题,有更简单的方法吗?

fn main() {
    let a = Matrix::Array(array![[0.0, 1.0], [2.0, 3.0]]);
    let b = Matrix::Array(array![[4.0, 5.0], [6.0, 7.0]]);
    let c1 = 0.001f64;
    let c2 = 0.5f64;
    let ci = &a + c1 * c2 * &b;
}

#[derive(Debug, PartialEq, Clone)]
pub enum Matrix {
    Raw(Vec<Vec<f64>>),
    RawPureDiagonal(Vec<f64>),
    RawSparse(Vec<(usize, usize, f64)>),
    Array(Array2<f64>),
    PureDiagonal(Array1<f64>),
}

impl<'a, 'b> Add<&'b Matrix> for &'a Matrix {
    type Output = Matrix;
    // --snip--
}

impl<'a> Add<Matrix> for &'a Matrix {
    type Output = Matrix;

    fn add(self, other: Matrix) -> Matrix {
        match (self, &other) {
            // ndarray库运算规则, &Array2 + &Array2 不消耗任何变量,生成一个新的Array2
            (Matrix::Array(x), Matrix::Array(y)) => {Matrix::Array(x + y)},
            _ => {panic!("Unsupported operations!")},
        }
    }
}

impl<'a> Mul<f64> for &'a Matrix {
    type Output = Matrix;
    // --snip--
}

impl Mul<f64> for Matrix {
    type Output = Matrix;
    // --snip--
}

impl<'a> Mul<&'a Matrix> for f64 {
    type Output = Matrix;

    fn mul(self, other: &'a Matrix) -> Matrix {
        match (self, other) {
            // ndarray库运算规则, &Array2 * f64 只消耗f64,生成一个新的Array2
            (x, Matrix::Array(y)) => {Matrix::Array(x * y)},
            _ => {panic!("Unsupported operations!")},
        }
    }   
}

impl Mul<Matrix> for f64 {
    type Output = Matrix;
    // --snip--
}

评论区

写评论
作者 eweca 2021-01-02 10:53

感谢!不过宏对我来说有点难,我有空尽量看看源代码的宏看看能不能理解完之后抄抄。不过不能只能对于&Matrix的重载啊。如a = &[b] + &[c] * &[d],假设全部生成Matrix,显然算到第二步就是a = &[b] + [cd],因为&[c] * &[d]返回了Matrix。所以我被迫得把所有的都写完。

--
👇
Aya0wind: 第二种部分合理,a+b从常理来看就是应该生成一个新的矩阵的。所以其实你只要写一个对于&Matrix的运算符重载就行了,但是Add这个没必要写,你在函数里面没有消耗,就没必要所有权,而且返回的肯定不能是&Matrix而是Matrix。

impl<'a, 'b> Add<&'b Matrix> for &'a Matrix {
    type Output = Matrix;

    fn add(self, rhs: &'b Matrix) -> Self::Output {
        unimplemented!()
    }
}

就写上面这一个就行。

另外运算符两边互换不等价这个是没问题的,用宏其实是普遍做法,你可以看看标准库,这类东西全都用宏做的,直接用一个宏把一堆类型的trait一起实现了,不然工作量很大而且重复代码很多。

作者 eweca 2021-01-02 10:46

感谢!有道理,是我想岔了。显然假如ADD函数里生成了一个Matrix而只传出一个&Matrix的话,编译器是不会允许通过的,因为这会是一个空指针。同理,第一种方法的直接传入Matrix&Matrix进行处理,不需要考虑drop的问题。因为传入的Matrix会在函数完成时自动销毁,而计算出的新Matrix会被返回。

至于自更新这个我得琢磨一下。可以省内存还是不错的,而且自更新按道理比生成新的快一点。

--
👇
Neutron3529: 第二种办法是只允许&Matrix参与运算,得到的答案也是&Matrix。

这是不可能的

你不可能从函数里传出一个引用作为返回值。

最多&Matrix参与运算,返回Matrix

或者&mut Matrix参与运算,返回&mut Matrix(以便接下来的处理)

作者 eweca 2021-01-02 10:33

嗯嗯。不过L+R逻辑与R+L有差异貌似我是用不到了。

--
👇
ezlearning: 关于Drop,可以看看这里哈: https://doc.rust-lang.org/stable/nomicon/drop-flags.html

运算符重载,LHS + RHS 不必须等于 RHS + LHS: https://doc.rust-lang.org/rust-by-example/trait/ops.html

Aya0wind 2021-01-02 00:36

第二种部分合理,a+b从常理来看就是应该生成一个新的矩阵的。所以其实你只要写一个对于&Matrix的运算符重载就行了,但是Add这个没必要写,你在函数里面没有消耗,就没必要所有权,而且返回的肯定不能是&Matrix而是Matrix。

impl<'a, 'b> Add<&'b Matrix> for &'a Matrix {
    type Output = Matrix;

    fn add(self, rhs: &'b Matrix) -> Self::Output {
        unimplemented!()
    }
}

就写上面这一个就行。

另外运算符两边互换不等价这个是没问题的,用宏其实是普遍做法,你可以看看标准库,这类东西全都用宏做的,直接用一个宏把一堆类型的trait一起实现了,不然工作量很大而且重复代码很多。

Neutron3529 2021-01-01 22:43

第二种办法是只允许&Matrix参与运算,得到的答案也是&Matrix。

这是不可能的

你不可能从函数里传出一个引用作为返回值。

最多&Matrix参与运算,返回Matrix

或者&mut Matrix参与运算,返回&mut Matrix(以便接下来的处理)

ezlearning 2021-01-01 22:00

关于Drop,可以看看这里哈: https://doc.rust-lang.org/stable/nomicon/drop-flags.html

运算符重载,LHS + RHS 不必须等于 RHS + LHS: https://doc.rust-lang.org/rust-by-example/trait/ops.html

1 共 6 条评论, 1 页