我在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 …
为了确定我是否可以/应该使用 Rust 而不是默认的 C/C++,我正在研究各种边缘情况,主要考虑到这个问题:在 0.1% 的情况下,它确实很重要,我总能得到编译器输出与 gcc 一样好(具有适当的优化标志)?答案很可能是否定的,但让我们看看......
Reddit上有一个相当特殊的示例,研究无分支排序算法的子例程的编译器输出。
这是基准 C 代码:
#include <stdint.h>
#include <stdlib.h>
int32_t* foo(int32_t* elements, int32_t* buffer, int32_t pivot)
{
size_t buffer_index = 0;
for (size_t i = 0; i < 64; ++i) {
buffer[buffer_index] = (int32_t)i;
buffer_index += (size_t)(elements[i] < pivot);
}
}
Run Code Online (Sandbox Code Playgroud)
这是带有编译器输出的godbolt 链接。
Rust 的第一次尝试如下所示:
pub fn foo0(elements: &Vec<i32>, mut buffer: [i32; 64], pivot: i32) -> () {
let mut buffer_index: usize = 0;
for i in 0..buffer.len() {
buffer[buffer_index] …Run Code Online (Sandbox Code Playgroud) 在阅读 Rust 文档时,我偶然发现了使用 while 循环(带有索引)迭代数组的代码a:
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index += 1;
}
}
Run Code Online (Sandbox Code Playgroud)
文档说:
... 这种方法容易出错;如果索引长度不正确,我们可能会导致程序崩溃。它也很慢,因为编译器添加了运行时代码以在循环的每次迭代中对每个元素执行条件检查。
第一个原因不言自明。第二个原因是我感到困惑的地方。
此外,他们建议为此使用 for 循环。
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
}
Run Code Online (Sandbox Code Playgroud)
我似乎无法理解这一点。Rust 编译器是否有某种行为?