diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index a8be892611..7a2abe115b 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -93,14 +93,11 @@ jobs: matrix: platform: ${{ fromJson(needs.prepare.outputs.platforms) }} debug: [false] - mimalloc: [false] include: - platform: linux/amd64 - platform: linux/amd64 debug: true - - platform: linux/amd64 - mimalloc: true - name: Build ${{ matrix.platform }} static musl binary${{ matrix.debug && ' (debug)' || '' }}${{ matrix.mimalloc && ' (mimalloc)' || '' }} + name: Build ${{ matrix.platform }} static musl binary${{ matrix.debug && ' (debug)' || '' }} runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} needs: [prepare] steps: @@ -141,25 +138,24 @@ jobs: uses: docker/bake-action@v6 with: pull: true - load: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug || matrix.mimalloc }} + load: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug }} source: . targets: static-builder-musl set: | ${{ matrix.debug && 'static-builder-musl.args.DEBUG_SYMBOLS=1' || '' }} - ${{ matrix.mimalloc && 'static-builder-musl.args.MIMALLOC=1' || '' }} ${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-musl.args.NO_COMPRESS=1' || '' }} *.tags= *.platform=${{ matrix.platform }} - ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope={0}-static-builder-musl{1}{2}', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} - ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope=refs/heads/main-static-builder-musl{0}{1}', matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} - ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-to=type=gha,scope={0}-static-builder-musl{1}{2},ignore-error=true', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} - ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope={0}-static-builder-musl{1}', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '') }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope=refs/heads/main-static-builder-musl{0}', matrix.debug && '-debug' || '') }} + ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-to=type=gha,scope={0}-static-builder-musl{1},ignore-error=true', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '') }} + ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} env: SHA: ${{ github.sha }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600 name: Export metadata - if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc + if: fromJson(needs.prepare.outputs.push) && !matrix.debug run: | mkdir -p /tmp/metadata @@ -169,7 +165,7 @@ jobs: env: METADATA: ${{ steps.build.outputs.metadata }} - name: Upload metadata - if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc + if: fromJson(needs.prepare.outputs.push) && !matrix.debug uses: actions/upload-artifact@v6 with: name: metadata-static-builder-musl-${{ steps.prepare.outputs.sanitized_platform }} @@ -181,9 +177,9 @@ jobs: # shellcheck disable=SC2034 # TODO: remove "containerimage.config.digest" fallback once all runners use buildx v0.18+ # which replaced it with "containerimage.digest" and "containerimage.descriptor" - digest=$(jq -r '."static-builder-musl" | ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '."containerimage.digest"' || '(."containerimage.config.digest" // ."containerimage.digest")' }}' <<< "${METADATA}") - docker create --platform="${PLATFORM}" --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" - docker cp "static-builder-musl:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}" + digest=$(jq -r '."static-builder-musl" | ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug) && '."containerimage.digest"' || '(."containerimage.config.digest" // ."containerimage.digest")' }}' <<< "${METADATA}") + docker create --platform="${PLATFORM}" --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" + docker cp "static-builder-musl:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}" env: METADATA: ${{ steps.build.outputs.metadata }} BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }} @@ -192,12 +188,12 @@ jobs: if: ${{ !fromJson(needs.prepare.outputs.push) }} uses: actions/upload-artifact@v6 with: - name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} - path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} + name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }} + path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }} compression-level: 0 - name: Upload assets if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag') - run: gh release upload "${REF}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} --repo dunglas/frankenphp --clobber + run: gh release upload "${REF}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }} --repo dunglas/frankenphp --clobber env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REF: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }} @@ -216,7 +212,7 @@ jobs: "${BINARY}" list-modules | grep http.handlers.vulcain "${BINARY}" php-cli -r "echo 'Sanity check passed';" env: - BINARY: ./frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} + BINARY: ./frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }} build-linux-gnu: permissions: diff --git a/alpine.Dockerfile b/alpine.Dockerfile index 31650af4c6..5b791295da 100644 --- a/alpine.Dockerfile +++ b/alpine.Dockerfile @@ -138,9 +138,11 @@ ENV GODEBUG=cgocheck=0 # copy watcher shared library (libgcc and libstdc++ are needed for the watcher) COPY --from=builder /usr/local/lib/libwatcher* /usr/local/lib/ -RUN apk add --no-cache libstdc++ && \ +RUN apk add --no-cache libstdc++ mimalloc2 && \ ldconfig /usr/local/lib +ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 + COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp RUN setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \ frankenphp version && \ diff --git a/build-static.sh b/build-static.sh index 429c8376ef..71ee53825b 100755 --- a/build-static.sh +++ b/build-static.sh @@ -21,7 +21,7 @@ os="$(uname -s | tr '[:upper:]' '[:lower:]')" # - FRANKENPHP_VERSION: FrankenPHP version (default: current Git commit) # - EMBED: Path to the PHP app to embed (default: none) # - DEBUG_SYMBOLS: Enable debug symbols if set to 1 (default: none) -# - MIMALLOC: Use mimalloc as the allocator if set to 1 (default: none) +# - MIMALLOC: Use mimalloc as the allocator if set to 1 (default: none, used on musl-linux) # - XCADDY_ARGS: Additional arguments to pass to xcaddy # - RELEASE: [maintainer only] Create a GitHub release if set to 1 (default: none) @@ -38,6 +38,9 @@ fi if [ -z "${SPC_LIBC}" ]; then if [ "${os}" = "linux" ]; then SPC_LIBC="musl" + if [ -z "${MIMALLOC}" ]; then + MIMALLOC=1 + fi fi fi # init spc build additional args diff --git a/docs/docker.md b/docs/docker.md index 99e2bb0f52..300f7d7384 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -277,6 +277,25 @@ WORKDIR /app ENTRYPOINT ["/usr/local/bin/frankenphp", "run", "-c", "/etc/caddy/Caddyfile"] ``` +## Mimalloc + +The Alpine Linux Docker images use [mimalloc](https://github.com/microsoft/mimalloc) as the default memory allocator. +This improves performance compared to musl's default allocator in threaded environments. + +To disable mimalloc, unset the `LD_PRELOAD` environment variable: + +```dockerfile +FROM dunglas/frankenphp:alpine + +ENV LD_PRELOAD='' +``` + +Or at runtime: + +```console +docker run -e LD_PRELOAD= dunglas/frankenphp:alpine +``` + ## Development Versions Development versions are available in the [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) Docker repository. diff --git a/docs/performance.md b/docs/performance.md index 5ca732d7f7..1a6cceeca2 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -41,9 +41,11 @@ especially when compiled in ZTS mode (thread-safe), which is required for Franke Also, [some bugs only happen when using musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl). -In production environments, we recommend using FrankenPHP linked against glibc, compiled with an appropriate optimization level. +To mitigate musl's performance issues in threaded environments, the Alpine Docker images and static binaries use [mimalloc](https://github.com/microsoft/mimalloc) as the default memory allocator. -This can be achieved by using the Debian Docker images, using [our maintainers .deb, .rpm, or .apk packages](https://pkgs.henderkes.com), or by [compiling FrankenPHP from sources](compile.md). +In production environments, we still recommend using FrankenPHP linked against glibc, compiled with an appropriate optimization level. + +This can be achieved by using the Debian Docker images, using [our maintainers .deb or .rpm packages](https://pkgs.henderkes.com), or by [compiling FrankenPHP from sources](compile.md). For leaner or more secure containers, you may want to consider [a hardened Debian image](docker.md#hardening-images) rather than Alpine. diff --git a/docs/static.md b/docs/static.md index 516acb3e91..38f55beffc 100644 --- a/docs/static.md +++ b/docs/static.md @@ -27,10 +27,10 @@ docker buildx bake --load static-builder-musl docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl ``` -For better performance in heavily concurrent scenarios, consider using the [mimalloc](https://github.com/microsoft/mimalloc) allocator. +The resulting binary will be using the [mimalloc](https://microsoft.github.io/mimalloc/) allocator for better performance. If you wish to use musl's built-in allocator, set `MIMALLOC=0`: ```console -docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl +docker buildx bake --load --set static-builder-musl.args.MIMALLOC=0 static-builder-musl ``` ### glibc-Based, Mostly Static Build (With Dynamic Extension Support) @@ -126,7 +126,7 @@ script to customize the static build: - `CLEAN`: when set, libphp and all its dependencies are built from scratch (no cache) - `NO_COMPRESS`: don't compress the resulting binary using UPX - `DEBUG_SYMBOLS`: when set, debug-symbols will not be stripped and will be added to the binary -- `MIMALLOC`: (experimental, Linux-only) replace musl's mallocng by [mimalloc](https://github.com/microsoft/mimalloc) for improved performance. We only recommend using this for musl targeting builds, for glibc prefer disabling this option and using [`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) when you run your binary instead. +- `MIMALLOC`: (Linux-only, default to `1` for musl) use [mimalloc](https://github.com/microsoft/mimalloc) for improved performance. For glibc, we recommend using [`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) when you run your binary instead. - `RELEASE`: (maintainers only) when set, the resulting binary will be uploaded on GitHub ## Extensions diff --git a/static-builder-musl.Dockerfile b/static-builder-musl.Dockerfile index 673a2fc44f..7454f19db6 100644 --- a/static-builder-musl.Dockerfile +++ b/static-builder-musl.Dockerfile @@ -23,7 +23,7 @@ ENV SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="${XCADDY_ARGS}" ARG CLEAN='' ARG EMBED='' ARG DEBUG_SYMBOLS='' -ARG MIMALLOC='' +ARG MIMALLOC=1 ARG NO_COMPRESS='' ENV GOTOOLCHAIN=local