5

I'm currently working on a simple project to get myself familiar with Rust. I don't have much systems programming experience but I'm hoping to learn!

I'm trying to create a Matrix struct but I'm finding it hard to figure out how I should store the data. I feel like I should be able to use an array. The size of the matrix must be defined on construction and so I would hope I can store the array on the stack.

Right now my code looks like this:

use std::ops::Mul;
use std::ops::Add;
use std::ops::Div;

struct Matrix {
    cols: i32,
    rows: i32,
    // Of course this doesn't work!
    data: [f32; ..cols*rows]
}

// Below here are a bunch of stub methods.
impl Mul<f32> for Matrix {
    type Output = Matrix;

    fn mul(self, m: f32) -> Matrix {
        return self;
    }
}

impl Mul<Matrix> for Matrix {
    type Output = Matrix;

    fn mul(self, m: Matrix) -> Matrix {
        // Will use Strassen algorithm if large, traditional otherwise
        return self;
    }
}

impl Add<Matrix> for Matrix {
    type Output = Matrix;

    fn add(self, m: Matrix) -> Matrix {
        return self;
    }
}

impl Div<f32> for Matrix {
    type Output = Matrix;

    fn div(self, f: f32) -> Matrix {
        return self;
    }
}

2 Answers 2

5

The easiest way to do this would be with a Vec.

struct Matrix {
    cols: i32,
    rows: i32,
    data: Vec<f32>
}

impl Matrix {
    fn new(cols: i32, rows: i32) -> Matrix {
        Matrix {
            cols: cols,
            rows: rows,
            data: vec![0.0; cols * rows]
        }
    }
}

If you don't want to store the data on the heap, you could make Matrix into a dynamically sized type, but this is hard to do and isn't really supported. See a Reddit thread on that topic.

If you don't want to use a Vec but are okay with storing the data on the heap, you could use a boxed slice instead (Box<[f32]>). See Vec::into_boxed_slice for one way to create one.

And of course, if you really don't want to use the heap, you could make different Matrix types for different sizes of matrices. This is what the crate nalgebra does.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for the response - it's helped me understand what's going on a bit more. Dynamic sizing looks plausible but I agree it's a hassle and I'm not sure it would let me do things like make the default matrix all zeros (without feeding in an array to the constructor). I would like to have the data on the stack but for now I'll try an implementation using boxed slices. Thanks!
I checked out the code for nalgebra. The problem is I'm planning on using this in a machine learning setting - so matrices of huge sizes will be common D:
@user124784 Probably best to use the heap then, since storing huge things on the stack is generally a bad idea.
Small advice: If you want to do serious work with huge matrices, you should consider using a library. Making matrix operations (very) fast is difficult and popular libraries are usually at least ten times faster than something a normal intelligent programmer could write.
Lukas - I'm using this project as a way to get familiar with Rust (and systems programming). I'm not expecting to make anything that can be used for serious applications :). Thank you for taking the time to comment though.
0

You may also use a vector of vector (Vec<Vec<T>>), without implementing your own Matrix or using a library. But this is slighly slower than Adrian's approach in terms of indexing. nalgebra is good at vectorized computation, but it performs worse than simple matrices made of Vec<T> or Vec<Vec<T>> when it comes to elementwise operation. Here are some benchmarks:

test bench_vec               ... bench:  84,694,933 ns/iter (+/- 7,412,836)
test bench_vec_of_vec        ... bench:  87,083,636 ns/iter (+/- 1,171,842)
test bench_vec_unsafe        ... bench:  41,440,947 ns/iter (+/- 752,463)
test bench_vec_of_vec_unsafe ... bench:  44,532,595 ns/iter (+/- 629,209)
test bench_nalgebra          ... bench: 452,872,630 ns/iter (+/- 40,284,295)
#![feature(test)]

extern crate test;
use na::Matrix;
use na::{Dynamic, VecStorage};
use nalgebra as na;

type DMatrixi32 = Matrix<u8, Dynamic, Dynamic, VecStorage<u8, Dynamic, Dynamic>>;

use test::Bencher;

#[bench]
fn bench_vec_of_vec(b: &mut Bencher) {
    let (m, n) = (10000, 10000);
    let mut matrix = vec![vec![0u8; n]; m];
    b.iter(|| {
        for i in 0..m {
            for j in 0..n {
                matrix[i][j] = 1u8;
            }
        }
    });
}

#[bench]
fn bench_vec(b: &mut Bencher) {
    let (m, n) = (10000, 10000);
    let mut matrix = vec![0u8; n * m];
    b.iter(|| {
        for i in 0..m {
            for j in 0..n {
                matrix[i * n + j] = 1u8;
            }
        }
    });
}

#[bench]
fn bench_vec_of_vec_unsafe(b: &mut Bencher) {
    let (m, n) = (10000, 10000);
    let mut matrix = vec![vec![0u8; n]; m];
    b.iter(|| {
        for i in 0..m {
            for j in 0..n {
                unsafe {
                    *matrix.get_unchecked_mut(i).get_unchecked_mut(j) = 1u8;
                }
            }
        }
    });
}

#[bench]
fn bench_vec_unsafe(b: &mut Bencher) {
    let (m, n) = (10000, 10000);
    let mut matrix = vec![0u8; n * m];
    b.iter(|| {
        for i in 0..m {
            for j in 0..n {
                unsafe { *matrix.get_unchecked_mut(i * n + j) = 1u8 };
            }
        }
    });
}

#[bench]
fn bench_nalgebra(b: &mut Bencher) {
    let (m, n) = (10000, 10000);
    let mut matrix = DMatrixi32::from_vec(m, n, vec![0u8; n * m]);
    b.iter(|| {
        for i in 0..m {
            for j in 0..n {
                matrix[(i, j)] = 1u8;
            }
        }
    });
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.