我在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)
}
}
Run Code Online (Sandbox Code Playgroud)
在紧密的循环中,我认为跳过边界检查可能会更快,所以我提供了一个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))
}
Run Code Online (Sandbox Code Playgroud)
奇怪的是,当我使用这些方法来实现矩阵乘法(通过行和列迭代器)时,我的基准测试显示,当我检查边界时它实际上快了大约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 …
我来自C,我想知道Rust的边界检查会影响性能.每次访问可能需要一些额外的汇编指令,这在处理大量数据时可能会受到影响.
另一方面,处理器性能代价高昂的是内存,因此更多的算术汇编指令可能不会受到影响,但是在加载缓存行之后,顺序访问应该非常快.
有人对此进行了基准测试吗?
作为练习,我尝试对 Rust 1.3.0 中的代码进行微优化。我在数组上有一个循环。像这样的东西:
loop {
for i in 0..arr.len() {
// something happens here
}
}
Run Code Online (Sandbox Code Playgroud)
由于 Rust 中的数组大小是固定的,编译器是否会通过arr.len()仅计算一次并重用该值来优化代码,还是会在顶层循环的每次传递中计算表达式?这个问题可以扩展到除了arr.len().
换句话说,上面的代码是否等同于:
let arr_len = arr.len();
loop {
for i in 0..arr_len {
// something happens here
}
}
Run Code Online (Sandbox Code Playgroud)