我正在用 Rust 编写一个线性代数库。
我有一个函数可以在给定的行和列中获取对矩阵单元的引用。此函数以行和列在界限内的一对断言开始:
#[inline(always)]
pub fn get(&self, row: usize, col: usize) -> &T {
assert!(col < self.num_cols.as_nat());
assert!(row < self.num_rows.as_nat());
unsafe {
self.get_unchecked(row, col)
}
}
在紧密循环中,我认为跳过边界检查可能会更快,所以我提供了一个get_unchecked
方法:
#[inline(always)]
pub unsafe fn get_unchecked(&self, row: usize, col: usize) -> &T {
self.data.get_unchecked(self.row_col_index(row, col))
}
奇怪的是,当我使用这些方法来实现矩阵乘法(通过行和列迭代器)时,我的基准测试表明,当我检查边界时,它实际上快了大约 33%。为什么会这样?
我在两台不同的计算机上试过这个,一台运行 Linux,另一台运行 OSX,都显示了效果。
完整代码在 github 上。相关文件是lib.rs。感兴趣的函数是:
get
在第 68 行get_unchecked
在第 81 行next
在第 551 行mul
在第 796 行matrix_mul
(基准)在第 1038 行
请注意,我使用类型级别的数字来参数化我的矩阵(也可以通过虚拟标记类型选择动态大小),因此基准是两个 100x100 矩阵相乘。
更新:
我已经大大简化了代码,删除了基准测试中没有直接使用的东西并删除了通用参数。我还编写了一个不使用迭代器的乘法实现,并且该版本不会产生相同的效果。有关此版本的代码,请参见此处。克隆minimal-performance
分支并运行cargo bench
将对乘法的两种不同实现进行基准测试(请注意,断言在该分支开始时已被注释掉)。
另外值得注意的是,如果我更改get*
函数以返回数据的副本而不是引用(f64
而不是&f64
),效果就会消失(但代码会稍微慢一点)。