優化 Rust 構建的策略及與 GitHub Actions 合併

爲什麼 Rust 的構建時間很慢?

Rust 是一種高性能且對開發人員友好的編程語言,Rust 因其安全性、速度、併發性和可編程性而在編程界獲得了關注和讚譽。它也被證明是構建企業應用程序的絕佳選擇。與 C/C++ 等語言相比,Rust 對於項目來說更容易上手,因爲 C/C++ 不那麼內存安全,開發人員需要多年的經驗才能開始工作,因此 Rust 被證明是一個更好的選擇。

近年來,它也是開發社區中最受歡迎的編程語言,因爲它爲編程語言做出了創新和高效的設計,例如零成本抽象和所有權,關注性能而不以可編程性爲代價。

由於語言設計的本質,Rust 構建 / 編譯時間相當慢,可能會阻礙開發人員的生產力:引入緩慢的反饋循環,作爲編譯時間的直接產物。下面的漫畫總結了編譯時間的問題,並且與這個場景非常相關。開發人員等待代碼編譯的時間越長,他們在產品上的工作就越少。這將影響整個發佈時間,並可能在整個產品發佈過程中造成蝴蝶效應。

爲了幫助緩解這個問題,這篇文章將介紹各種優化 Rust 構建時間的策略,然後將這些策略與 GitHub Actions 合併。這將使 Rust 開發人員能夠更快地進行迭代,從而幫助他們高效地完成項目。

Rust 中緩慢構建速度的蝴蝶效應

緩慢的構建會在幾個方面顯著影響開發速度和生產力。在時間至關重要的企業項目中,這可能會對團隊的工程習慣、發佈節奏和未來的產品規劃產生重大影響。如果應用程序構建太慢且不允許開發人員快速迭代,則會有以下影響:

更長的反饋循環

緩慢的構建導致更長的反饋循環。開發人員必須等待構建過程完成,然後才能測試更改或接收自動測試結果。如果使用的是 Uffizzi,那麼在這種情況下,構建和部署拉取請求的預覽環境也可能需要更長的時間。

緩慢的構建時間 (平均 20 分鐘) 導致上下文切換到其他任務也需要等待。這破壞了開發流程。當在構建和測試之後回到開發過程時,開發人員必須花一點額外的時間重新構建代碼庫,然後在程序上進行重複這個過程。

阻礙合作

由於較長的反饋循環,工程團隊成員之間可能會出現脫節。這是由於單個開發人員自己花費了更長的時間進行開發,導致整個團隊的速度比他們應該的要慢。在 sprint 中,同行之間共享的知識並不多,這減少了協作,減緩了產品的增長。

減少部署頻率

由於沒有及時發佈足夠的 bug 修復和特性,因此降低了總體部署頻率。如果客戶一直在等待某些錯誤修復或特定功能,這將直接影響客戶滿意度。業務敏捷性也會受到影響,因爲業務本身不能足夠快地達到設定的目標,並向客戶提供理想的產品。反過來,作爲研發和發佈週期緩慢的直接產物,影響了企業對市場變化的反應能力。緩慢的發佈週期意味着新功能得不到足夠快的反饋。

降低代碼質量

當構建花費很長時間時,開發人員在發佈當天就會被時間所束縛,並且可能不太傾向於編寫好的代碼,從而導致低質量的代碼合併。這可能導致總體上較低的代碼質量,並增加引入錯誤或迴歸的可能性。

增加 CI/CD 成本

產品構建時間直接影響 CI/CD 成本。通過在構建過程中減少幾分鐘的時間,可以節省很多錢。這對於擁有大型項目或多個應用程序的企業來說尤其成問題,因爲 CI/CD 成本影響會以數量級增加。

優化 Rust 應用程序構建的策略

以下優化 Rust 構建的策略各有利弊,由用戶決定在他們的構建用例中什麼最適合。用戶需要考慮他們正在優化的構建是開發、發佈、測試還是其他構建。找出每種構建優化的正確組合有助於用戶順利開發和發佈。

Rust 應用程序的發佈構建往往比開發人員構建要慢得多。這是由於編譯器在發佈構建期間進行了優化,使應用程序的二進制文件儘可能最小。最後,用戶必須決定自己的構建優化選擇。以下是優化 Rust 應用程序構建的策略。這些策略可以相互配合使用:

有效的緩存利用率

緩存是最直接的,也是加快構建時間最關鍵的。通過緩存 target 目錄和 cargo registry,可以顯著減少編譯依賴項所花費的時間。

對於上述緩存配置,可以使用流行的 https://github.com/Swatinem/rust-cache 來簡化爲 Rust 應用程序構建設置和使用緩存的過程。

- name: Cache dependencies
  uses: Swatinem/rust-cache@v2.2.1

在介紹了上面的基本依賴項緩存之後,可以使用更智能的緩存 sccache 作爲編譯器緩存工具。它充當編譯器包裝器,並儘可能避免編譯。在這種情況下,確保我們不僅緩存了依賴項,而且還緩存不需要在每次構建時重新編譯的編譯時構件。

- 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/

並行編譯

Rust 支持開箱即用的並行編譯,這允許利用多核處理器的強大功能,以成倍地加快構建過程。要啓用並行編譯,請在 config.toml 中設置 codegen-units 選項。

[profile.dev]
codegen-units = 1

代碼單元或代碼生成單元是將代碼分成幾個部分,以便並行地對每個部分執行編譯,這將大大提高編譯速度。這樣做的缺點是,如果代碼沒有被分解和逐塊編譯,代碼將無法得到優化。

增加代碼單元的數量可能會導致錯過一些潛在的優化,但可以通過將該值設置爲 1 來優化運行時性能。這意味着代碼庫將被視爲單個代碼片段,並且不會進行並行編譯。

[profile.release]
codegen-units = 1

覆蓋配置文件

Rust 中的構建系統預定義了一組配置選項,這些集合稱爲 profiles。默認情況下,Rust 爲不同的目的使用不同的構建配置文件。

例如,在開發過程中構建項目時將使用 dev 配置文件。此配置文件優先考慮更快的構建時間,並啓用損害性能的調試語句。要使用 dev 配置文件進行構建,請在命令行中運行 cargo build。該命令不需要任何標誌來指定這是一個開發構建,因爲這是默認的構建選項。

release 的目的是在向外界發佈應用程序的最終版本時使用。因此,自然地,這個 release 以較慢的編譯時間爲代價,優先考慮生成二進制文件的速度。要使用發佈配置文件進行構建,只需在項目的根目錄中使用 cargo build --release。

可以根據用戶的需要,通過向 config.toml 添加配置來覆蓋這些默認配置文件。例如,要降低 release 的優化級別,可以配置以下內容:

[profile.release]
opt-level = 2
codegen-units = 16

上面的配置將 opt-level(優化級別) 從 3(默認值) 減少到 2。

opt-level 是一個編譯器設置,它控制優化過程中應用的級別,其中級別用數字表示。以下是設置及其含義:

除了設置 opt-level 之外,代碼單元設置增加到 16,允許在編譯期間進行更多的並行化。

在 Github Actions 中應用配置,允許更快的發佈構建以及有效的緩存利用率

考慮這樣一個項目,它需要構建短暫預覽優化版本。這個構建必須比通常的 Rust release 構建更快地完成,並且不需要完全優化,以便更快地創建 Rust 應用程序二進制文件,然後可以在短暫的環境中用於測試。

Rust 構建配置

考慮到上述情況,優化級別可以降低,不必是最高的,所以我們可以將 opt-level 設置爲 2 而不是 3(默認值)。考慮到我們希望構建仍然快一點,讓我們通過將 codegen-units 設置爲 4 來應用一些並行編譯。對於臨時環境構建來說,這是一個很好的配置,但創建自定義配置文件更有意義。

要創建自定義配置文件,讓我們向 Cargo.toml 添加以下內容,它將創建一個名爲 ephemeral-build 的新構建配置文件,其中包含我們需要的配置。

[profile.ephemeral-build]
opt-level = 2
codegen-units = 8

要使用臨時構建配置文件,必須將其設置爲在進行發佈構建時使用的默認配置文件。這可以通過爲 Rust 設置 --cfg 標誌來完成,通過一個環境變量 RUSTFLAGS 導出該標誌及其相關值,該變量將在運行時讀取。

RUSTFLAGS=”--cfg profile=ephemeral-build” cargo build --release

Dockerfile 配置

以最可移植的方式發佈應用程序的最佳方式是通過容器映像。下面的 Dockerfile 只接受構建應用程序的二進制文件,考慮到緩存優化是在 Github Actions 中完成的,image 構建器不必再擔心構建和緩存了。所有需要做的就是將二進制文件複製到 image 中,然後 image 就可以使用了,非常簡單。

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 配置

可以一起使用上述所有配置來創建應用程序映像的構建管道。構建管道通過使用前面提到的策略進行了優化,並且還生成了包含應用程序二進制文件的容器映像。下面是 Github Action 管道的樣子:

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

上面的 Github Actions 配置優化了 Rust 應用程序的構建,專門用於臨時環境。最終的應用程序構建經過了足夠的優化,可以很容易地進行測試,並且構建得足夠快,從而不會在迭代之間花費太多時間。這對於短暫的環境構建來說是完美的。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_yrna33VMTyxOHVG09lW1Q