diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 6dde9aa8..1cf71caa 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -219,6 +219,11 @@ jobs: ls -l /tmp/aisix-shipped test "$(nm /tmp/aisix-shipped | grep -c ' [tT] ')" -gt 1000 readelf -S /tmp/aisix-shipped | grep -E '\.symtab|\.eh_frame' + # jemalloc must actually be linked (prefixed _rjem_ symbols): + # if the cfg gates in aisix-server drift, the binary silently + # falls back to glibc malloc with no other signal. + nm /tmp/aisix-shipped | grep -q ' [tT] _rjem_' \ + || { echo "::error::jemalloc symbols missing from shipped binary"; exit 1; } - name: Install cosign if: github.event_name != 'pull_request' diff --git a/Cargo.lock b/Cargo.lock index 379292ac..c8bf9748 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,6 +453,7 @@ dependencies = [ "serde_json", "socket2 0.5.10", "tempfile", + "tikv-jemallocator", "tokio", "tracing", "uuid", @@ -4860,6 +4861,26 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" version = "0.3.47" diff --git a/Cargo.toml b/Cargo.toml index 83ec4804..4b884a3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,7 +189,10 @@ tempfile = "3.13" # CARGO_PROFILE_RELEASE_STRIP=none cargo build --release # See issue #847. [profile.release] -lto = "thin" +# Fat LTO over thin: cross-crate inlining across the whole graph is a +# measured per-request win on the saturation grid, paid for once per +# release build in link time. Numbers in the PR that flipped it. +lto = "fat" codegen-units = 1 strip = "debuginfo" diff --git a/Dockerfile b/Dockerfile index 509e90dc..80e69ed3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -71,6 +71,11 @@ COPY schemas ./schemas # `--locked` forces the build to use the exact versions in Cargo.lock — # fails fast if the lockfile is stale rather than silently resolving # fresh deps in CI. +# +# If this ever builds for linux/arm64: jemalloc bakes the build host's +# page size into the binary, and QEMU reports 4K — set +# JEMALLOC_SYS_WITH_LG_PAGE=16 here or the image aborts at startup on +# 64K-page kernels (see crates/aisix-server/src/main.rs). RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/src/target \ cargo build --locked --release --bin aisix \ diff --git a/crates/aisix-server/Cargo.toml b/crates/aisix-server/Cargo.toml index 99472d9b..fb7d48fc 100644 --- a/crates/aisix-server/Cargo.toml +++ b/crates/aisix-server/Cargo.toml @@ -56,6 +56,13 @@ socket2 = { version = "0.5", features = ["all"] } # axum-server itself depends on, so the builder types unify. hyper-util = { version = "0.1", features = ["server-auto", "tokio"] } +# jemalloc as the global allocator, only on the targets we ship and +# bench (Linux glibc — the Docker image and both supported production +# arches). Other targets (macOS dev builds, musl) keep the system +# allocator rather than carry an allocator we never run in production. +[target.'cfg(all(target_os = "linux", target_env = "gnu"))'.dependencies] +tikv-jemallocator = "0.6" + [dev-dependencies] tempfile = "3" wiremock = "0.6" diff --git a/crates/aisix-server/src/main.rs b/crates/aisix-server/src/main.rs index 41996c25..1e4edaf8 100644 --- a/crates/aisix-server/src/main.rs +++ b/crates/aisix-server/src/main.rs @@ -16,6 +16,18 @@ use std::error::Error as StdError; use std::path::{Path, PathBuf}; use std::sync::Arc; +// jemalloc as the global allocator on the shipped/benched targets +// (Linux glibc): under the thread-per-core saturation load, allocator +// time drops from ~15% of request CPU (glibc malloc) to ~6%. Other +// targets keep the system allocator. One deploy caveat: jemalloc bakes +// the build host's page size into the binary, so an aarch64 binary +// built on a 4K-page host aborts at startup on a 64K-page kernel — +// cross-building for such kernels needs JEMALLOC_SYS_WITH_LG_PAGE=16, +// which runs on both page sizes. +#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[global_allocator] +static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + mod cert_bundle; mod export; mod heartbeat;