5

2022-45: 使用 sccache 加快 Rust 编译速度

 1 year ago
source link: https://xuanwo.io/reports/2022-45/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2022-45: 使用 sccache 加快 Rust 编译速度

sccache 是由 mozilla 开发的编译缓存工具,设计思路是作为一个编译的 wrapper,把编译产物放在保存到存储后端上,尽可能避免重复编译,从而加快整体的编译速度。sccache 支持的编译后端包括 gcc, clang, MSVC, rustc, NVCC 等等。以 Rust 编译为例,可以这样来使用它:

export RUSTC_WRAPPER=/path/to/sccache
cargo build

最近社区提出有没有办法加快 databend 的编译速度,我想到了可以使用 sccache 来加速,优势如下:

  • Databend 依赖众多,而大部分依赖很少会变化,他们都能够被很好的缓存下来
  • Databend 的 dev 构建跑在 AWS 的 Self-hosted Runner 上,而且本身就已经配置好了内网的 S3 bucket

各方面条件都很完善,看来只需要写一个 Github Action 就好了。事实证明我太乐观了:

github-action.png

主要坑的地方有两块:

  • 为了方便测试环境的维护,Databend CI 统一使用了 build-tools 镜像来跑测试,搭配 sccache 起来需要额外做不少配置
  • 云平台出于安全考虑启用了基于节点 Role 的认证,没有配置 AK/SK,sccache 没有支持 IMDSv2

第一个问题通过不停地骚扰 @everpcpc 得到了解决,后面这个问题相对更麻烦。我们首先给 sccache 提交了一个 Issue: Support IMDSv2 for AWS IAM Role authentication,然后开始尝试自己解决问题。

调试 sccache

调试 sccache 的过程非常痛苦:sccache 是一个 CS 的架构,用户每次调用 sccache 的时候都会在后台启动一个 server,通过 client 和 server 之间的通信来决定是否使用缓存。所有跟存储后端的交互都是由 server 来执行的,在编译过程中没法直接看到日志,只能把日志重定向到某个文件,然后看文件的输出。此外 sccache 的历史比较悠久,很多的地方都不太利于调试,比如:

let res = self
    .client
    .execute(request)
    .await
    .with_context(move || format!("failed GET: {}", url))?;

if res.status().is_success() {
    let body = res.bytes().await.context("failed to read HTTP body")?;
    info!("Read {} bytes from {}", body.len(), url2);

    Ok(body.into_iter().collect())
} else {
    Err(BadHttpStatusError(res.status()).into())
}

请求失败的时候只会打出状态码,没有实际错误内容,在调试 IMDSv2 时走了很多弯路。为了尽快解决问题,我直接把 sccache fork 了过来,把底层的存储修改成了使用 opendal:

- let credentials = self
-     .provider
-     .credentials()
-     .await
-     .context("failed to get AWS credentials")?;
-
- let bucket = self.bucket.clone();
- let _ = bucket
-     .put(&key, data, &credentials)
-     .await
-     .context("failed to put cache entry in s3")?;
+ self.op.object(&key).write(data).await?;

这下舒服多了,很快定位到了问题并修复掉,现在 Databend 已经成功使用了 sccache 了。在安装好 sccache 之后,只需要配置如下:

- name: Setup Build Tool
    uses: ./.github/actions/setup_build_tool
    with:
    image: ${{ inputs.target }}
    bypass_env_vars: RUSTFLAGS,RUST_LOG,RUSTC_WRAPPER,SCCACHE_BUCKET,SCCACHE_S3_KEY_PREFIX,SCCACHE_S3_USE_SSL,AWS_DEFAULT_REGION,AWS_REGION,AWS_ROLE_ARN,AWS_STS_REGIONAL_ENDPOINTS,AWS_WEB_IDENTITY_TOKEN_FILE

- name: Build Debug
    shell: bash
    run: cargo -Z sparse-registry build --target ${{ inputs.target }}
    env:
    RUSTC_WRAPPER: /opt/rust/cargo/bin/sccache
    SCCACHE_BUCKET: databend-ci
    SCCACHE_S3_KEY_PREFIX: cache/
    SCCACHE_S3_USE_SSL: true

这里记录一些比较简单的数据:首次全量编译的时候需要花费一些额外的时间上传编译产物,后续的编译就可以复用这些产物,不再需要编译了。

  • 没有任何 cache 的时候: 6m 20s
  • 首次编译
    • Finished dev [unoptimized + debuginfo] target(s) in 9m 04s
  • 第二次编译
    • Finished dev [unoptimized + debuginfo] target(s) in 5m 35s
  • 去除 debug 日志后
    • Finished dev [unoptimized + debuginfo] target(s) in 4m 38s

这里的首次编译时间还需要考虑 sccache debug 日志对性能的影响,实际上应该不会增长那么多。不过 Databend 还是有很多地方没有办法缓存,后面可以看一些有没有能优化的地方。我给上游提交了一个 proposal: Use opendal to handle the cache storage operations,看看能不能把 sccache 的存储后端访问改为使用 opendal,这样对接存储和调试起来就更方便了~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK