【发布时间】:2016-09-27 15:34:04
【问题描述】:
我正在用 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,都显示了效果。
完整代码为on github。相关文件为lib.rs。感兴趣的函数是:
-
get第 68 行 -
get_unchecked第 81 行 -
next第 551 行 -
mul第 796 行 -
matrix_mul(基准)在第 1038 行
请注意,我使用类型级别的数字来参数化我的矩阵(也可以通过虚拟标记类型选择动态大小),因此基准是两个 100x100 矩阵相乘。
更新:
我已经显着简化了代码,删除了基准测试中未直接使用的内容并删除了通用参数。我还编写了一个不使用迭代器的乘法实现,并且该版本不会产生相同的效果。有关此版本的代码,请参阅 here。克隆minimal-performance 分支并运行cargo bench 将对乘法的两种不同实现进行基准测试(请注意,断言在该分支开始时已被注释掉)。
另外值得注意的是,如果我更改 get* 函数以返回数据副本而不是引用(f64 而不是 &f64),效果就会消失(但代码会稍微慢一点)。
【问题讨论】:
-
老问题又来了:您是否使用编译器优化进行编译(带有
--release标志)? ;) -
对生锈一无所知:您的基准测试是否合理?缓存效果、方差、测试数据同步...
-
@LukasKalbertodt 是的,我使用
cargo bench运行我的基准测试,它会随着发布自动编译。 -
当你检查边界时,也许编译器可以更积极地优化?
-
对于类似的东西,最好在发布模式下编译两个独立的二进制文件,并使用
objdump -D来识别相关紧密循环中使用的机器指令。
标签: performance optimization rust llvm-codegen