为什么Rust的构建时间很慢?
Rust是一种高性能且对开发人员友好的编程语言,Rust因其安全性、速度、并发性和可编程性而在编程界获得了关注和赞誉。它也被证明是构建企业应用程序的绝佳选择。与C/C++等语言相比,Rust对于项目来说更容易上手,因为C/C++不那么内存安全,开发人员需要多年的经验才能开始工作,因此Rust被证明是一个更好的选择。
近年来,它也是开发社区中最受欢迎的编程语言,因为它为编程语言做出了创新和高效的设计,例如零成本抽象和所有权,关注性能而不以可编程性为代价。
由于语言设计的本质,Rust构建/编译时间相当慢,可能会阻碍开发人员的生产力:引入缓慢的反馈循环,作为编译时间的直接产物。下面的漫画总结了编译时间的问题,并且与这个场景非常相关。开发人员等待代码编译的时间越长,他们在产品上的工作就越少。这将影响整个发布时间,并可能在整个产品发布过程中造成蝴蝶效应。
Rust中缓慢构建速度的蝴蝶效应
缓慢的构建会在几个方面显著影响开发速度和生产力。在时间至关重要的企业项目中,这可能会对团队的工程习惯、发布节奏和未来的产品规划产生重大影响。如果应用程序构建太慢且不允许开发人员快速迭代,则会有以下影响:
更长的反馈循环
缓慢的构建导致更长的反馈循环。开发人员必须等待构建过程完成,然后才能测试更改或接收自动测试结果。如果使用的是Uffizzi,那么在这种情况下,构建和部署拉取请求的预览环境也可能需要更长的时间。
缓慢的构建时间(平均20分钟)导致上下文切换到其他任务也需要等待。这破坏了开发流程。当在构建和测试之后回到开发过程时,开发人员必须花一点额外的时间重新构建代码库,然后在程序上进行重复这个过程。
阻碍合作
由于较长的反馈循环,工程团队成员之间可能会出现脱节。这是由于单个开发人员自己花费了更长的时间进行开发,导致整个团队的速度比他们应该的要慢。在sprint中,同行之间共享的知识并不多,这减少了协作,减缓了产品的增长。
减少部署频率
由于没有及时发布足够的bug修复和特性,因此降低了总体部署频率。如果客户一直在等待某些错误修复或特定功能,这将直接影响客户满意度。业务敏捷性也会受到影响,因为业务本身不能足够快地达到设定的目标,并向客户提供理想的产品。反过来,作为研发和发布周期缓慢的直接产物,影响了企业对市场变化的反应能力。缓慢的发布周期意味着新功能得不到足够快的反馈。
降低代码质量
当构建花费很长时间时,开发人员在发布当天就会被时间所束缚,并且可能不太倾向于编写好的代码,从而导致低质量的代码合并。这可能导致总体上较低的代码质量,并增加引入错误或回归的可能性。
增加CI/CD成本
优化Rust应用程序构建的策略
以下优化Rust构建的策略各有利弊,由用户决定在他们的构建用例中什么最适合。用户需要考虑他们正在优化的构建是开发、发布、测试还是其他构建。找出每种构建优化的正确组合有助于用户顺利开发和发布。
Rust应用程序的发布构建往往比开发人员构建要慢得多。这是由于编译器在发布构建期间进行了优化,使应用程序的二进制文件尽可能最小。最后,用户必须决定自己的构建优化选择。以下是优化Rust应用程序构建的策略。这些策略可以相互配合使用:
有效的缓存利用率
缓存是最直接的,也是加快构建时间最关键的。通过缓存target目录和cargo registry,可以显著减少编译依赖项所花费的时间。
缓存target目录:该目录包含构建的构件,缓存它将节省后续构建的时间。
缓存cargo registry:这确保依赖项不会被不必要地重新下载或重新编译。
- name: Cache dependencies
uses: Swatinem/rust-cache@v2.2.1
- name: Configure sccache
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo “SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.2
上面的一组github action设置了sccache环境变量,其中RUSTC_WRAPPER指示要使用哪个编译器包装器,SCCACHE_GHA_ENABLED设置sccache以使用github action缓存。
要了解有关sccache的更多信息,请查看https://github.com/mozilla/sccache/
并行编译
[profile.dev]
codegen-units = 1
代码单元或代码生成单元是将代码分成几个部分,以便并行地对每个部分执行编译,这将大大提高编译速度。这样做的缺点是,如果代码没有被分解和逐块编译,代码将无法得到优化。
[profile.release]
codegen-units = 1
覆盖配置文件
Rust中的构建系统预定义了一组配置选项,这些集合称为profiles。默认情况下,Rust为不同的目的使用不同的构建配置文件。
例如,在开发过程中构建项目时将使用dev配置文件。此配置文件优先考虑更快的构建时间,并启用损害性能的调试语句。要使用dev配置文件进行构建,请在命令行中运行cargo build。该命令不需要任何标志来指定这是一个开发构建,因为这是默认的构建选项。
release的目的是在向外界发布应用程序的最终版本时使用。因此,自然地,这个release以较慢的编译时间为代价,优先考虑生成二进制文件的速度。要使用发布配置文件进行构建,只需在项目的根目录中使用cargo build --release。
[profile.release]
opt-level = 2
codegen-units = 16
上面的配置将opt-level(优化级别)从3(默认值)减少到2。
opt-level是一个编译器设置,它控制优化过程中应用的级别,其中级别用数字表示。以下是设置及其含义:
opt-level = 0:没有优化。此设置优先考虑快速编译时间,使其适合于开发和调试,并牺牲运行时性能。
opt-level = 1:基本优化。在编译速度和运行时性能之间提供平衡,这对开发期间的增量构建很有好处。
opt-level = 2:更高级别的优化。以较慢的编译时间为代价,提高生成的二进制文件的运行时性能,用于版本构建,优化程度略低于最佳水平。
opt-level = 3:最高级别的优化。着重于最大化所生成二进制文件的性能。结果导致编译时间明显变慢,并且由于激进的优化而使调试变得困难。
在Github Actions中应用配置,允许更快的发布构建以及有效的缓存利用率
考虑这样一个项目,它需要构建短暂预览优化版本。这个构建必须比通常的Rust release构建更快地完成,并且不需要完全优化,以便更快地创建Rust应用程序二进制文件,然后可以在短暂的环境中用于测试。
Rust构建配置
考虑到上述情况,优化级别可以降低,不必是最高的,所以我们可以将opt-level设置为2而不是3(默认值)。考虑到我们希望构建仍然快一点,让我们通过将codegen-units设置为4来应用一些并行编译。对于临时环境构建来说,这是一个很好的配置,但创建自定义配置文件更有意义。
[profile.ephemeral-build]
opt-level = 2
codegen-units = 8
RUSTFLAGS=”--cfg profile=ephemeral-build” cargo build --release
Dockerfile配置
FROM alpine:latest
RUN apk update --quiet \
&& apk add -q --no-cache libgcc tini curl
COPY target/x86_64-unknown-linux-musl/release/app /bin/app
RUN ln -s /bin/app /app
ENTRYPOINT ["app"]
在上面的配置中,在必要的包更新之后,只有二进制文件被复制,然后进行符号链接以获得更好的访问。
Github Actions配置
name: Rust application ephemeral environment build
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: x86_64-unknown-linux-musl
- name: Configure sccache env var and set build profile to ephemeral build
run: |
echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
echo “SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
echo “RUSTFLAGS=’--cfg profile=ephemeral-build’” >> $GITHUB_ENV
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.2
- name: Run build
uses: actions-rs/cargo@v1
with:
command: build
args: --target x86_64-unknown-linux-musl --release
在上面启动管道时,发生的第一件事是Checkout存储库。
下一步将安装Rust。在这里,x86_64-unknown-linux-musl目标用于安装和构建,对于我们最终的容器映像构建,我们使用的基本映像是alpine:latest,为了让我们的应用程序在alpine容器中运行,我们需要将其构建到MUSL目标。
设置必要的环境变量
使用sccache用作Rust编译器包装器,RUSTC_WRAPPER使用Github Actions缓存。
RUSTFLAGS用于设置在进行发布构建时使用ephemeral-build进行构建。