编译时间

虽然这本书主要是关于提高 Rust 程序性能的,但本节讨论的是减少 Rust 程序的编译时间,因为这是许多人感兴趣的相关话题。

最小化编译时间 部分讨论了通过构建配置选择来减少编译时间的方法。本节的其余部分讨论了需要修改程序代码来减少编译时间的方法。

可视化

Cargo 具有一项功能,可以让您可视化程序的编译过程。使用以下命令构建:

cargo build --timings

完成后,它将打印一个 HTML 文件的名称。在 Web 浏览器中打开该文件。它包含一个 甘特图,显示了程序中各个 crate 之间的依赖关系。这显示了您的 crate 图中有多少并行性,这可能表明是否应该拆分任何序列化编译的大型 crate。有关如何阅读图表的详细信息,请参阅 文档

LLVM 中间代码

Rust 编译器使用 LLVM 作为其后端。LLVM 的执行可能是编译时间的一个很大部分,特别是当 Rust 编译器的前端生成大量 IR 时,LLVM 需要很长时间来优化。

这些问题可以通过 cargo llvm-lines 进行诊断,它显示了哪些 Rust 函数导致生成最多的 LLVM IR。通常最重要的是泛型函数,因为它们在大型程序中可能被实例化几十甚至几百次。

如果一个泛型函数导致 IR 膨胀,有几种修复方法。最简单的方法就是使函数变小。 示例 1, 示例 2

另一种方法是将函数的非泛型部分移动到一个单独的、非泛型的函数中,这个函数只会被实例化一次。是否可能取决于泛型函数的细节。当可能时,非泛型函数通常可以写成泛型函数内部的内部函数,如下面 std::fs::read 的代码所示:

pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
    fn inner(path: &Path) -> io::Result<Vec<u8>> {
        let mut file = File::open(path)?;
        let size = file.metadata().map(|m| m.len()).unwrap_or(0);
        let mut bytes = Vec::with_capacity(size as usize);
        io::default_read_to_end(&mut file, &mut bytes)?;
        Ok(bytes)
    }
    inner(path.as_ref())
}

示例

有时常见的实用函数,如 Option::mapResult::map_err,会被实例化多次。将它们替换为等效的 match 表达式可以帮助编译时间。

这些改变对编译时间的影响通常会很小,尽管偶尔可能会很大。 示例

这些改变还可以减少二进制文件的大小。