【发布时间】:2021-12-31 03:00:12
【问题描述】:
我正在尝试在 Rust 中实现一个相当快的 Floyd-Warshall 算法版本。该算法在有向加权图中找到所有顶点之间的最短路径。
算法的主要部分可以这样写:
// dist[i][j] contains edge length between vertices [i] and [j]
// after the end of the execution it contains shortest path between [i] and [j]
fn floyd_warshall(dist: &mut [Vec<i32>]) {
let n = dist.len();
for i in 0..n {
for j in 0..n {
for k in 0..n {
dist[j][k] = min(dist[j][k], dist[j][i] + dist[i][k]);
}
}
}
}
此实现非常简短且易于理解,但它的运行速度比类似的 c++ 实现慢 1.5 倍。
据我了解,问题是在每个向量访问时,Rust 都会检查索引是否在向量的边界内,这会增加一些开销。
我用 get_unchecked* 函数重写了这个函数:
fn floyd_warshall_unsafe(dist: &mut [Vec<i32>]) {
let n = dist.len();
for i in 0..n {
for j in 0..n {
for k in 0..n {
unsafe {
*dist[j].get_unchecked_mut(k) = min(
*dist[j].get_unchecked(k),
dist[j].get_unchecked(i) + dist[i].get_unchecked(k),
)
}
}
}
}
}
它的运行速度确实提高了 1.5 倍 (full code of the test)。
我没想到边界检查会增加这么多开销:(
是否可以在没有不安全的情况下以惯用的方式重写此代码,使其与不安全版本一样快?例如。是否可以通过在代码中添加一些断言来向编译器“证明”不会有越界访问?
【问题讨论】:
-
您是否与 Vecs 数组(或其他任何东西)结婚?我的第一个想法是切换到一个合适的二维数组,或者失败,一个手动索引的一维数组。然后,您可以通过断言一维数组的长度为 n*n 来说服编译器放弃边界检查。
-
另外,你是用
--release编译的,对吧? -
是的,@Jmb,我正在发布模式下编译。
-
我不是 Rust 程序员,所以我不知道。在幕后,LLVM 似乎不理解 2D 数组,而且这个 C++ 测试程序也没有像希望的那样优化,所以我对回答这个问题的前景感到悲观:
#include <cassert> void test(int n) { assert(n >= 0); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { assert(i + j < n + n); } } }。
标签: algorithm rust graph-algorithm