Skip to content

Conversation

@ainame
Copy link
Contributor

@ainame ainame commented Oct 26, 2025

Background

Closes #6298

Currently static Linux binary distributed isn't as performant as expected. This is because malloc in musl libc, which is used in Static Linux SDK, doesn't work well with multiple threaded execution. It uses futex syscalls to safely access memory, which in turns, decrease performance in multi-threaded env.

https://git.musl-libc.org/cgit/musl/tree/src/thread/__wait.c?h=v1.1.23&id=7e399fabd3db2c528b5982803eeba2841f547695
http://git.musl-libc.org/cgit/musl/tree/src/malloc/malloc.c?h=v1.1.23&id=7e399fabd3db2c528b5982803eeba2841f547695

What

To address this issue, I introduced mimalloc for static Linux binary in this PR.
https://github.com/microsoft/mimalloc

mimalloc is performant memory allocator written in C that works as drop-in replacement for malloc and supports builds for musl. Compared with other popular allocators like jemalloc or tcmalloc, It think it's fair option. Also, in Swift compiler project (swiftlang/swift), mimaloc has been adopted for the allocator in Windows's toolchain (not runtime!).

https://forums.swift.org/t/se-454-adopt-mimalloc-for-windows-toolchain/

How

Since mimalloc is only necessary when building static Linux binary, I added a new step that compiles libmimalloc.a to release.yml and updated swift build command.

swift build \
    -c release \
    --product swiftlint \
    --swift-sdk ${{ matrix.swift_sdk }} \
    -Xswiftc -DSWIFTLINT_DISABLE_SOURCEKIT \
    -Xlinker -z -Xlinker stack-size=0x80000 \
    -Xlinker --whole-archive \
    -Xlinker $(pwd)/libmimalloc.a \
    -Xlinker --no-whole-archive

The mimalloc README recommends placing mimalloc.o (instead of the .a) at the very beginning of the linker input so its malloc overrides the standard one. With swift build, we can’t reliably force an absolute “first” position.
https://github.com/microsoft/mimalloc?tab=readme-ov-file#static-override

As a practical alternative, I link against libmimalloc.a and wrap it with --whole-archive/--no-whole-archive so all relevant objects are included, provided the library is built with the malloc override enabled. To be fair, the .o approach works fine for now. I just wanted to make sure it will keep linking correctly in the future.

Since mimalloc is MIT license, I've also updated Makefile to include mimalloc's license file into the zip files for Linux binary. third_party_licenses/mimalloc-LICENSE is now bundled in repo.

Test

GitHub Actions

I ran release.yml on my fork and worked successfully.
https://github.com/ainame/SwiftLint/actions/runs/18823390591

Artefacts

It runs faster with mimalloc.

swiftlint-static-arm64 with mimalloc from https://github.com/ainame/SwiftLint/actions/runs/18823390591

ainame@ainame-vm:~/repos/SwiftLint$ time /home/ainame/Downloads/swiftlint-static-arm64/swiftlint . --no-cache
Linting Swift files at paths .
Linting 'Path+Helpers.swift' (1/670)
...
real	0m1.342s
user	0m6.289s
sys	0m0.088s

swiftlint-static-arm64 built from main branch synced with upstream from https://github.com/ainame/SwiftLint/actions/runs/18819592629/artifacts/4374908255

ainame@ainame-vm:~/repos/SwiftLint$ time '/home/ainame/Downloads/swiftlint-static-arm64(1)/swiftlint' . --no-cache
Linting Swift files at paths .
Linting 'Path+Helpers.swift' (1/670)
Linting 'SwiftLintCommandPlugin.swift' (2/670)
....
real	0m4.445s
user	0m14.040s
sys	0m9.510s

Symbols

nm suggests that malloc/free/calloc/realloc are resolved from mimalloc. They are defined in the final binary (symbol type T).

ainame@ainame-vm:~/repos/SwiftLint$ nm -C --defined-only .build/aarch64-swift-linux-musl/release/swiftlint | grep --color=auto -E "(T|W) (malloc|free|calloc|realloc)$"
0000000002878df0 T calloc
0000000002877de0 T free
0000000002878b70 T malloc
0000000002879870 T realloc

@ainame ainame marked this pull request as ready for review October 26, 2025 22:36
Copy link
Collaborator

@SimplyDanny SimplyDanny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow! That's awesome. 🤩 I compared the ARM binaries at least and it turns out that the static ones are now as fast as the dynamic ones. So great! Thank you very much, @ainame!

@ainame
Copy link
Contributor Author

ainame commented Oct 27, 2025

Testing the changes here https://github.com/ainame/SwiftLint/actions/runs/18858095838/job/53810600398


It ran successfully after those changes!
https://github.com/ainame/SwiftLint/releases/tag/0.0.5

Confirmed mimalloc's license files still bundles.
Screenshot 2025-10-27 at 22 59 20

@ainame
Copy link
Contributor Author

ainame commented Oct 27, 2025

@SimplyDanny Thank you for your review! I've addressed your suggestions and updated CHANGELOG.md!
Feel free to make additional changes or merge this PR. Looking forward to using static Linux binary🙂

@SimplyDanny SimplyDanny enabled auto-merge (squash) October 28, 2025 09:18
@SimplyDanny SimplyDanny merged commit 8ee8523 into realm:main Oct 28, 2025
23 checks passed
@ainame ainame deleted the mimalloc branch October 28, 2025 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The performance of static Linux binary is bad

2 participants