From c125e9115554b10c7e093f810754985bfba51b56 Mon Sep 17 00:00:00 2001 From: David de Hilster Date: Tue, 25 Aug 2026 12:41:57 -0400 Subject: [PATCH] ci: cache vcpkg packages and compile each target in parallel Breakdown of a 15m43s Windows CI job on #718: Install 3rd Party (vcpkg builds ICU) 10m04s Cmake Build all targets 3m40s checkout + generate solution 40s all 14 regression tests ~3s The tests are not what makes CI slow. Two thirds of it is vcpkg rebuilding ICU from source on every run, and most of the rest is the engine compiling one file at a time. 1. vcpkg binary caching. `vcpkg install` ran with no binary source configured, so nothing was reused between runs. Point VCPKG_BINARY_SOURCES at a files-based cache under the workspace and persist it with actions/cache, keyed on the vcpkg submodule commit (which pins the ports) plus vcpkg.json (which picks them). vcpkg verifies each package's ABI hash itself, so a partial restore-keys hit is safe: whatever does not match is simply rebuilt. Measured cost today: 10m04s on Windows, 4m11s on Linux, per job, per run. 2. /MP on MSVC. `cmake --build --parallel N` with the Visual Studio generator becomes msbuild /m:N, which parallelises PROJECTS -- so lite's 101 sources (Arun.cpp alone is ~14k lines) compiled serially no matter what --parallel said. Same defect fixed for generated analyzer code in 3.8.7, in the engine's own build. Clean Win32 Release build with --parallel 4: without /MP 189s with /MP4 49s (/MP4 to model a 4-core runner honestly) Also drops the "List $RUNNER_WORKSPACE before build" step: it recursively listed and printed the entire workspace, costing 8s and a very large log for no current purpose. No version bump: this changes how the engine is built and tested, not what it does. The binary is unaffected. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-linux.yml | 26 +++++++++++++++++++++++ .github/workflows/build-macos.yml | 26 +++++++++++++++++++++++ .github/workflows/build-windows.yml | 32 +++++++++++++++++++++++++---- CMakeLists.txt | 7 +++++++ 4 files changed, 87 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 2fb47502..5d6dbdb1 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -34,6 +34,9 @@ jobs: env: VCPKG_DEFAULT_TRIPLET: x64-linux VERSION_SUFFIX: ${{ matrix.version_suffix }} + # Re-use compiled vcpkg packages between runs; otherwise every run + # rebuilds ICU from source (measured: 4m11s on Linux, 10m04s on Windows). + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}/vcpkg-bincache,readwrite" steps: - name: Checkout Repository @@ -97,6 +100,29 @@ jobs: shell: bash working-directory: vcpkg + # The submodule commit pins the ports, so it decides what the built + # packages are; vcpkg.json decides which ones. Key on both. vcpkg checks + # each package's ABI hash itself, so a partial restore-keys hit is safe: + # anything that does not match is simply rebuilt. + - name: Resolve vcpkg commit for cache key + id: vcpkgkey + shell: bash + run: echo "sha=$(git -C vcpkg rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Cache vcpkg binary packages + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/vcpkg-bincache + key: vcpkg-${{ runner.os }}-${{ matrix.id }}-${{ steps.vcpkgkey.outputs.sha }}-${{ hashFiles('vcpkg.json') }} + restore-keys: | + vcpkg-${{ runner.os }}-${{ matrix.id }}-${{ steps.vcpkgkey.outputs.sha }}- + vcpkg-${{ runner.os }}-${{ matrix.id }}- + + # vcpkg errors out if the binary-cache directory does not exist yet. + - name: Create vcpkg binary cache dir + shell: bash + run: mkdir -p "${{ github.workspace }}/vcpkg-bincache" + - name: Install 3rd Party (Non-20.04) if: matrix.id != 'docker-ubuntu-20.04' # Same latent mismatch as the Windows job: use the vcpkg the diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 2f543723..2365d2ba 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -14,6 +14,9 @@ jobs: name: Build NLP-ENGINE runs-on: macOS-latest env: + # Re-use compiled vcpkg packages between runs; otherwise every run + # rebuilds ICU from source (measured: 4m11s on Linux, 10m04s on Windows). + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}/vcpkg-bincache,readwrite" VCPKG_DEFAULT_TRIPLET: arm64-osx steps: @@ -36,6 +39,29 @@ jobs: - name: Install Brew run: brew install automake + # The submodule commit pins the ports, so it decides what the built + # packages are; vcpkg.json decides which ones. Key on both. vcpkg checks + # each package's ABI hash itself, so a partial restore-keys hit is safe: + # anything that does not match is simply rebuilt. + - name: Resolve vcpkg commit for cache key + id: vcpkgkey + shell: bash + run: echo "sha=$(git -C vcpkg rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Cache vcpkg binary packages + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/vcpkg-bincache + key: vcpkg-${{ runner.os }}-${{ runner.arch }}-${{ steps.vcpkgkey.outputs.sha }}-${{ hashFiles('vcpkg.json') }} + restore-keys: | + vcpkg-${{ runner.os }}-${{ runner.arch }}-${{ steps.vcpkgkey.outputs.sha }}- + vcpkg-${{ runner.os }}-${{ runner.arch }}- + + # vcpkg errors out if the binary-cache directory does not exist yet. + - name: Create vcpkg binary cache dir + shell: bash + run: mkdir -p "${{ github.workspace }}/vcpkg-bincache" + - name: Install 3rd Party run: ./vcpkg install working-directory: vcpkg diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index c59129af..17f21326 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -24,6 +24,11 @@ jobs: # that root with this repo. (Not the bug fixed below -- the steps were # running C:\vcpkg's SCRIPTS, not merely honoring its root.) VCPKG_ROOT: ${{ github.workspace }}/vcpkg + # Re-use compiled vcpkg packages between runs. Without this, every run + # rebuilds ICU from source: measured at 10m04s of a 15m43s job, which was + # about two thirds of CI. "clear;" drops the default sources so only our + # cache dir is consulted. + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}/vcpkg-bincache,readwrite" steps: - uses: actions/checkout@v4 @@ -44,6 +49,29 @@ jobs: run: .\bootstrap-vcpkg.bat working-directory: vcpkg + # The submodule commit pins the ports, so it decides what the built + # packages are; vcpkg.json decides which ones. Key on both. vcpkg checks + # each package's ABI hash itself, so a partial restore-keys hit is safe: + # anything that does not match is simply rebuilt. + - name: Resolve vcpkg commit for cache key + id: vcpkgkey + shell: bash + run: echo "sha=$(git -C vcpkg rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Cache vcpkg binary packages + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/vcpkg-bincache + key: vcpkg-${{ runner.os }}-${{ runner.arch }}-${{ steps.vcpkgkey.outputs.sha }}-${{ hashFiles('vcpkg.json') }} + restore-keys: | + vcpkg-${{ runner.os }}-${{ runner.arch }}-${{ steps.vcpkgkey.outputs.sha }}- + vcpkg-${{ runner.os }}-${{ runner.arch }}- + + # vcpkg errors out if the binary-cache directory does not exist yet. + - name: Create vcpkg binary cache dir + shell: bash + run: mkdir -p "${{ github.workspace }}/vcpkg-bincache" + - name: Install 3rd Party # Invoke the vcpkg the step above just bootstrapped, NOT whatever # "vcpkg" resolves to on PATH. The windows-latest runner ships a @@ -57,10 +85,6 @@ jobs: run: .\vcpkg.exe install working-directory: vcpkg - - name: List $RUNNER_WORKSPACE before build - shell: pwsh - run: Get-ChildItem -Path $env:RUNNER_WORKSPACE -Recurse -Force | Select-Object FullName - - name: Make build directory run: mkdir build diff --git a/CMakeLists.txt b/CMakeLists.txt index 39f02719..35b86f96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,13 @@ find_library(DL_LIBRARY dl) # Ensure dl library is found set(CMAKE_GENERATOR_PLATFORM Win32 HINT) if(MSVC) + # Compile the sources within each target in parallel. `cmake --build + # --parallel N` with the Visual Studio generator becomes msbuild /m:N, + # which parallelises PROJECTS -- so lite's 101 sources (Arun.cpp alone is + # ~14k lines) compiled one after another no matter what --parallel said. + # Measured on a clean Win32 Release build with --parallel 4: + # 189s without, 49s with /MP4 (modelling a 4-core CI runner). + add_compile_options(/MP) add_compile_options(/w44005 /w44244 /w44311 /w44211 /w44302 /w44267 /w44312 /w45033 /w44624 /w44996 /w44273) add_definitions(-D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING) add_definitions(-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS)