我正在使用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。感兴趣的函数包括:
- 在第68行的
get
- 在第81行的get_unchecked
- 在第551行的next
- 在第796行的mul
- 在第1038行的matrix_mul
(基准测试)请注意,我正在使用类型级别数字来参数化我的矩阵(也可以通过虚标记类型进行动态大小),因此基准测试正在将两个100x100矩阵相乘。
更新:
我大幅简化了代码,删除了与基准测试无关的内容并删除了通用参数。我还编写了一个不使用迭代器的乘法实现,该版本不会导致相同的效果。请参见此处以获取代码的此版本。克隆
minimal-performance
分支并运行cargo bench
将对两个不同的乘法实现进行基准测试(请注意,开始时断言被注释掉了)。还值得注意的是,如果我更改
get*
函数以返回数据的副本而不是引用(f64
而不是&f64
),则效果会消失(但整体速度略慢)。
objdump -D
来识别相关紧密循环中使用的机器指令。 - dpc.pw