microsoft/mimalloc

GitHub: microsoft/mimalloc

微软推出的一款高性能、紧凑型通用内存分配器,旨在替代标准 malloc 以提升多线程性能并提供可选的安全加固。

Stars: 12547 | Forks: 1066

[](https://dev.azure.com/Daan0324/mimalloc/_build?definitionId=1&_a=summary) # mimalloc   mimalloc (发音为 "me-malloc") 是一个具有出色[性能](#performance)特征的通用内存分配器。 最初由 Daan Leijen 为 [Koka](https://koka-lang.github.io) 和 [Lean](https://github.com/leanprover/lean) 语言的运行时系统开发。 最新版本 : `v3.2.8` (2026-02-03) 候选发布版 3,请报告任何问题。 最新 v2 版本: `v2.2.7` (2026-01-15)。 最新 v1 版本: `v1.9.7` (2026-01-15)。 mimalloc 是 `malloc` 的直接替代品,可以在其他程序中使用 无需修改代码,例如,在基于动态链接 ELF 的系统(Linux, BSD 等)上,您可以这样使用: ``` > LD_PRELOAD=/usr/lib/libmimalloc.so myprogram ``` 它还包含一种在 [Windows](#override_on_windows) 中动态覆盖默认分配器的方法。 设计的显著方面包括: - __小巧且一致__:该库大约有 1万行代码,使用简单且 一致的数据结构。这使得它非常适合 集成和适配到其他项目中。对于运行时系统,它 提供了单调_心跳_和延迟释放的钩子(用于 引用计数下的有界最坏情况时间)。 部分由于其简单性,mimalloc 已被移植到许多系统(Windows, macOS, Linux, WASM, 各种 BSD, Haiku, MUSL 等),并且对动态覆盖有出色的支持。 同时,它是一个工业级的分配器,在数千台机器上运行(非常)大规模 分布式服务,具有出色的最坏情况延迟。 - __空闲列表分片__:不是(每个大小类)一个大的空闲列表,我们有 每个“mimalloc 页”许多较小的列表,这减少了碎片并 增加了局部性—— 在时间上接近分配的东西在内存中也接近分配。 (一个 mimalloc 页包含一种大小类的块,在 64 位系统上通常为 64KiB)。 - __空闲列表多分片__:大想法!我们不仅将空闲列表按 mimalloc 页分片,而且对于每一页我们有多个空闲列表。特别是,有 一个列表用于线程本地 `free` 操作,另一个用于并发 `free` 操作。来自另一个线程的释放现在可以是单个 CAS,而不需要 线程之间复杂的协调。由于将有 数千个单独的空闲列表,争用自然分布在堆上, 并且在单个位置争用的概率将很低——这相当 类似于跳表等随机算法,其中添加 随机预言机消除了对更复杂算法的需求。 - __及早页面清除__:当一个“页面”变空时(由于空闲列表分片,机会增加) 内存被标记为 OS 未使用(重置或取消提交) 减少(实际)内存压力和碎片,尤其是在长时间运行的 程序中。 - __安全__:_mimalloc_ 可以在安全模式下构建,添加保护页, 随机化分配,加密空闲列表等,以防止各种 堆漏洞。在我们的基准测试中,性能损失通常平均约为 10%。 - __一等堆__:高效创建和使用多个堆以跨不同区域分配。 一个堆可以一次销毁,而不是单独释放每个对象。 新特性:v3 拥有真正的一等堆,可以从任何线程在堆中分配。 - __有界__:它不会遭受_爆炸_ \[1\],有有界的最坏情况分配 时间 (_wcat_)(直到 OS 原语),有界的空间开销(~0.2% 元数据,具有低 内部碎片),并且仅使用原子操作没有内部争用点。 - __快速__:在我们的基准测试中(见[下文](#performance)), _mimalloc_ 优于其他领先的分配器(_jemalloc_, _tcmalloc_, _Hoard_ 等), 并且通常使用更少的内存。一个很好的特性是它在广泛的 基准测试范围内表现 consistently 良好。对于较大的服务器程序也有良好的巨大 OS 页面支持。 [文档](https://microsoft.github.io/mimalloc)提供了 API 的完整概述。 您可以在[技术报告](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action)中阅读更多关于 mimalloc 设计的信息,其中也有详细的基准测试结果。 享受吧! ### 版本 mimalloc 有三个维护版本。除了 OS 内存的处理方式外,这些版本大体相同。 新开发主要集中在 v3,而 v1 和 v2 维护安全和错误修复。 - __v1__:mimalloc 的初始设计(发布标签:`v1.9.x`,开发分支 `dev`)。如果可能,请针对此版本提交 PR。 - __v2__:主要 mimalloc 版本。使用线程本地段来减少碎片。(发布标签:`v2.2.x`,开发分支 `dev2` 和 `main`) - __v3__:简化了以前版本的无锁设计并改进了线程间内存共享。 在某些大工作负载下,此版本可能使用 (更)少的内存。还支持真正的一等堆(可以从任何线程分配) 并且具有更高效的堆遍历(例如用于 CPython GC)。 (发布标签:`v3.2.x`,开发分支 `dev3`)。 ### 发布 * 2026-02-03, `v3.2.8` (rc3): 修复 macOS 上的线程重新初始化问题。修复旧版本 GCC 上的 SIMD 代码生成错误。将 Windows TLS 槽限制从 64 扩展到 1088。更精确地报告提交统计信息。 修复 arena 中空闲页搜索的问题。 * 2026-01-15, `v1.9.7`, `v2.2.7`, `v3.2.7` (rc2): 修复 OS 分配的块的零初始化问题。 对于 v3,进行了各种错误和性能修复。修复 Debian 32 位编译问题。 * 2026-01-08, `v1.9.6`, `v2.2.6`, `v3.2.6` (rc1): 重要的错误修复。对 v3 的许多改进,包括 真正的一等堆,可以从任何线程在堆中分配,以及跟踪每个堆的统计信息。 添加了 `MIMALLOC_ALLOW_THP` 选项。默认启用,Android 除外。当在 v3 上检测到 THP 时, mimalloc 会将 `MIMALLOC_MINIMAL_PURGE_SIZE` 设置为 2MiB,以避免破坏潜在的 THP 大页。 v3 在 Windows 上使用更快的 TLS 访问,并改进了 `mi_calloc` 和对齐分配的性能。 修复了旧版 v3 中罕见的竞争条件,修复了调试统计信息中潜在的缓冲区溢出,添加了用于返回分配和释放时的分配大小的 API。 * 2025-06-13, `v3.1.5`: 错误修复版本,修复了内存并不总是正确提交的问题 (问题 #1098)。 * 2025-06-09, `v1.9.4`, `v2.2.4`, `v3.1.4` (beta) : 一些重要的错误修复,包括 OS 内存 并不总是完全释放的情况。改进了 v3 性能,在 XBox 上构建,修复 Android 上的构建,支持旧版 macOS 的 interpose,在 macOS 上使用 MADV_FREE_REUSABLE,始终检查提交成功,更好地支持 Windows 固定 TLS 偏移等。 * 2025-03-28, `v1.9.3`, `v2.2.3`, `v3.0.3` (beta) : 各种小错误和构建修复,包括: 修复 arm32 pre v7 构建,修复 mingw 构建,获取运行时统计信息,改进统计提交计数,修复非 BMI1 x64 系统上的执行。 * 2025-03-06, `v1.9.2`, `v2.2.2`, `v3.0.2-beta`: 各种小错误和构建修复。 添加 `mi_options_print`, `mi_arenas_print`, 以及实验性的 `mi_stat_get` 和 `mi_stat_get_json`。 添加 `mi_thread_set_in_threadpool` 和 `mi_heap_set_numa_affinity` (仅 v3)。添加 vcpkg portfile。 升级 mimalloc-redirect 到 v1.3.2。`MI_OPT_ARCH` 现在默认关闭,但在 arm64 上仍假设 armv8.1-a 用于快速原子操作。添加 QNX 支持。 * 2025-01-03, `v1.8.9`, `v2.1.9`, `v3.0.1-alpha`: 临时版本。支持 Windows arm64。新的[受保护](#guarded)构建,可以将 OS 保护页放置在对象后面,以便在缓冲区溢出发生时捕获它们。 许多小修复:在 Windows arm64, cygwin, riscV, 和 dragonfly 上构建;修复 Windows 静态库初始化以考虑 线程本地析构函数(在 Rust/C++ 中);macOS 标签更改;macOS TLS 槽修复;改进统计信息; Windows 上一致的 `mimalloc.dll`(而不是 `mimalloc-override.dll`);在 Win11 H2 上修复 mimalloc-redirect; 向 canary 添加 0 字节;上游 CPython 修复;减少 .bss 大小;允许 Windows 上固定的 TLS 槽以提高性能。 * [更早的发布说明](#older-release-notes) 特别感谢: * Sergiy Kuryata 对减少内存提交的贡献——尤其是在使用 Windows 线程池的 Windows 上(现在在 v3 中实现)。 * [David Carlier](https://devnexen.blogspot.com/) (@devnexen) 对他的许多贡献,以及使 mimalloc 在许多不太常见的操作系统上工作得更好,如 Haiku, Dragonfly 等。 * Mary Feofanova (@mary3000), Evgeniy Moiseenko, 和 Manuel Pöter (@mpoeter) 使 mimalloc 可进行 TSAN 检查,并使用 [genMC] 模型检查器发现内存模型错误。 * Weipeng Liu (@pongba), Zhuowei Li, Junhua Wang, 和 Jakub Szymanski,感谢他们对 mimalloc 的早期支持和在大规模服务中的部署,促成了 mimalloc 算法在大工作负载下的许多改进。 * Jason Gibson (@jasongibson) 对大规模工作负载和服务器环境进行了详尽的测试,并发现了(早期版本的)`mimalloc` 中的复杂错误。 * Manuel Pöter (@mpoeter) 和 Sam Gross(@colesbury) 发现了被遗弃段回收中的 ABA 并发问题。Sam 还创建了 [no GIL](https://github.com/colesbury/nogil) Python 分支,其内部使用 mimalloc。 ### 用法 mimalloc 用于各种大规模低延迟服务和程序,例如: # 构建 ## Windows 在 Visual Studio 2022 中打开 `ide/vs2022/mimalloc.sln` 并构建。 `mimalloc-lib` 项目构建一个静态库(在 `out/msvc-x64` 中),而 `mimalloc-override-dll` 项目构建一个 DLL 用于覆盖整个程序中的 malloc。 ## Linux, macOS, BSD, 等。 我们使用 [`cmake`](https://cmake.org) 作为构建系统: ``` > mkdir -p out/release > cd out/release > cmake ../.. > make ``` 这将库构建为共享(动态)库(`.so` 或 `.dylib`),静态库(`.a`),以及作为单个目标文件(`.o`)。 `> sudo make install`(在 `/usr/local/lib` 和 `/usr/local/include` 中安装库和头文件) 您可以构建调试版本,它执行许多内部检查并维护详细的统计信息: ``` > mkdir -p out/debug > cd out/debug > cmake -DCMAKE_BUILD_TYPE=Debug ../.. > make ``` 这会将共享库命名为 `libmimalloc-debug.so`。 最后,您可以构建一个使用保护页、加密空闲列表等的_安全_版本: ``` > mkdir -p out/secure > cd out/secure > cmake -DMI_SECURE=ON ../.. > make ``` 这会将共享库命名为 `libmimalloc-secure.so`使用 `cmake ../.. -LH` 查看所有可用的构建选项。 示例使用默认编译器。如果您想使用另一个,请使用: ``` > CC=clang CXX=clang++ cmake ../.. ``` ## 使用 Visual Studio 的 Cmake 您也可以在 Windows 上使用 cmake。打开 Visual Studio 2022 开发提示符 并使用正确的[生成器](https://cmake.org/cmake/help/latest/generator/Visual%20Studio%2017%202022.html)和架构调用 `cmake`,例如: ``` > cmake ..\.. -G "Visual Studio 17 2022" -A x64 -DMI_OVERRIDE=ON ``` cmake 构建类型是在实际构建时指定的,例如: ``` > cmake --build . --config=Release ``` 您也可以在 Windows 上安装 [LLVM 工具集](https://learn.microsoft.com/en-us/cpp/build/clang-support-msbuild?view=msvc-170#install-1)以直接使用 `clang-cl` 编译器构建: ``` > cmake ../.. -G "Visual Studio 17 2022" -T ClangCl ``` ## 单一源文件 您也可以直接构建单个 `src/static.c` 文件作为项目的一部分,而完全不需要 `cmake`。确保也将 mimalloc `include` 目录添加到包含路径中。 # 使用库 首选用法是包含 ``,链接共享或静态库,并专门使用 `mi_malloc` API 进行分配。例如, ``` > gcc -o myprogram -lmimalloc myfile.c ``` mimalloc 仅使用安全的 OS 调用(`mmap` 和 `VirtualAlloc`),并且可以与链接到同一程序的其他分配器共存。 如果您使用 `cmake`,您可以简单地使用: ``` find_package(mimalloc 1.8 REQUIRED) ``` 在您的 `CMakeLists.txt` 中查找本地安装的 mimalloc。然后使用: ``` target_link_libraries(myapp PUBLIC mimalloc) ``` 链接共享(动态)库,或: ``` target_link_libraries(myapp PUBLIC mimalloc-static) ``` 链接静态库。有关示例,请参见 `test\CMakeLists.txt`。 为了在 C++ 程序中获得最佳性能,还建议覆盖全局 `new` 和 `delete` 运算符。为了方便,mimalloc 提供了 [`mimalloc-new-delete.h`](include/mimalloc-new-delete.h),它为您完成此操作——只需将其包含在您项目中的单个(!)源文件中。 在 C++ 中,mimalloc 还提供了 `mi_stl_allocator` 结构体,它实现了 `std::allocator` 接口。 您可以传递环境变量以打印详细消息(`MIMALLOC_VERBOSE=1`) 和统计信息(`MIMALLOC_SHOW_STATS=1`)(在调试版本中): ``` > env MIMALLOC_SHOW_STATS=1 ./cfrac 175451865205073170563711388363 175451865205073170563711388363 = 374456281610909315237213 * 468551 subproc 0 blocks peak total current block total# bin S 4: 75.3 KiB 55.2 MiB 0 32 B 1.8 M ok bin S 6: 31.0 KiB 180.4 KiB 0 48 B 3.8 K ok bin S 8: 64 B 64 B 0 64 B 1 ok bin S 9: 160 B 160 B 0 80 B 2 ok bin S 17: 1.2 KiB 1.2 KiB 0 320 B 4 ok bin S 21: 640 B 3.1 KiB 0 640 B 5 ok bin S 33: 5.0 KiB 5.0 KiB 0 5.0 KiB 1 ok binned : 84.2 Ki 41.5 Mi 0 ok huge : 0 0 0 ok total : 84.2 KiB 41.5 MiB 0 malloc req: 29.7 MiB pages peak total current block total# touched : 152.8 KiB 152.8 KiB 152.8 KiB pages : 8 14 0 ok abandoned : 1 249 0 ok reclaima : 0 reclaimf : 249 reabandon : 0 waits : 0 extended : 38 retire : 35 searches : 0.7 avg arenas peak total current block total# reserved : 1.0 GiB 1.0 GiB 1.0 GiB committed : 4.8 MiB 4.8 MiB 4.4 MiB reset : 0 purged : 385.5 Ki arenas : 1 rollback : 0 mmaps : 3 commits : 0 resets : 1 purges : 2 guarded : 0 heaps : 1 1 1 process peak total current block total# threads : 1 1 1 numa nodes: 1 elapsed : 0.553 s process : user: 0.557 s, system: 0.013 s, faults: 29, peak rss: 2.1 MiB, peak commit: 4.8 MiB ``` 然而,在已经使用标准 malloc 接口的现有程序中,上述使用 `mi_` 前缀 API 的模型并不总是可行的,另一个选择是完全覆盖标准 malloc 接口,并将所有调用重定向到 _mimalloc_ 库。 ## 环境选项 您可以通过编程方式(使用 [`mi_option_set`](https://microsoft.github.io/mimalloc/group__options.html))或通过环境变量设置更多选项: - `MIMALLOC_SHOW_STATS=1`: 程序终止时显示统计信息。 - `MIMALLOC_VERBOSE=1`: 显示详细消息(包括统计信息)。 - `MIMALLOC_SHOW_ERRORS=1`: 显示错误和警告消息。 高级选项: - `MIMALLOC_ARENA_EAGER_COMMIT=2`: 为 mimalloc 从中分配段和页面的大 arena(通常为 1GiB)开启即时提交。将其设置为 2(默认)以 仅在过量提交系统(例如 Linux)上启用此功能。将其设置为 1 以在其他系统(如 Windows 或 macOS)上显式启用,这可能会提高性能(因为整个 arena 一次性提交)。 请注意,即时提交只会增加提交,而不会增加实际的峰值驻留集,因此启用它通常是可以的。 - `MIMALLOC_PURGE_DELAY=N`: mimalloc 清除未使用的 OS 页面之前的延迟(以 `N` 毫秒为单位)(v3 中默认为 `1000`)。这向 OS 发出信号,表明底层物理内存可以被重用,这可以减少内存碎片,尤其是在长时间运行的(服务器)程序中。将 `N` 设置为 `0` 会在页面变为未使用时立即清除,这可以改善内存使用,但也会降低性能。 将其设置为 `-1` 可完全禁用清除。 - `MIMALLOC_PURGE_DECOMMITS=1`: 默认情况下,“清除”内存意味着未使用的内存被取消提交(Windows 上为 `MEM_DECOMMIT`,`mmap` 系统上为 `MADV_DONTNEED`(这会立即减少 rss))。将其设置为 0 以在清除时改为“重置”未使用的 内存(Windows 上为 `MEM_RESET`,通常 `mmap` 系统上为 `MADV_FREE`(这不会立即减少 rss))。 Mimalloc 通常不会“释放”OS 内存,而只是“清除”OS 内存,换句话说,它试图保留虚拟地址范围并在这些范围内取消提交(以使底层物理内存可用于其他进程)。 针对大工作负载和服务的更多选项: - `MIMALLOC_ALLOW_THP=1`: 默认情况下,在 Linux 系统上始终允许透明大页。仅在 Android 上,此选项默认关闭。当设置为 `0` 时,mimalloc 运行的进程将禁用 THP。如果启用,mimalloc 还会将 v3 中的 `MIMALLOC_MINIMAL_PURGE_SIZE` 设置为 2MiB,以避免潜在地破坏透明大页。 - `MIMALLOC_USE_NUMA_NODES=N`: 假装最多有 `N` 个 NUMA 节点。如果未设置,则会在运行时检测实际的 NUMA 节点。将 `N` 设置为 1 可能会避免某些虚拟环境中的问题。此外,将其设置为低于实际 NUMA 节点的数字是可以的,只会导致线程可能跨实际 NUMA 节点分配更多内存(但这在任何情况下都可能发生,因为 NUMA 本地分配始终是尽力而为,但无法保证)。 - `MIMALLOC_ALLOW_LARGE_OS_PAGES=0`: 设置为 1 以在可用时使用大 OS 页(2 或 4MiB);对于某些工作负载,这可以 显着提高性能。但是,大 OS 页无法被清除或与其他进程共享,因此在某些情况下可能会导致内存使用量增加。 使用 `MIMALLOC_VERBOSE` 检查大 OS 页是否已启用——通常需要 显式授予大 OS 页权限(如在 [Windows][windows-huge] 和 [Linux][linux-huge] 上)。然而,有时 OS 非常缓慢地为大 OS 页保留连续的物理内存,因此在可能存在内存碎片的系统上请谨慎使用(因此,我们通常建议尽可能改用 `MIMALLOC_RESERVE_HUGE_OS_PAGES`)。 - `MIMALLOC_RESERVE_HUGE_OS_PAGES=N`: 其中 `N` 是 1GiB _巨大_ OS 页的数量。这会在 启动时保留巨大页,有时这可以为大工作负载带来巨大的(延迟)性能提升。 通常最好不要将 `MIMALLOC_ALLOW_LARGE_OS_PAGES=1` 与此设置结合使用。就像大 OS 页一样,请谨慎使用,因为当内存碎片化时,保留 连续的物理内存可能需要很长时间(但保留巨大页仅在启动时进行一次)。 请注意,我们通常需要显式授予巨大 OS 页权限(如在 [Windows][windows-huge] 和 [Linux][linux-huge] 上))。 巨大页通常在 NUMA 节点之间均匀分配。 我们可以使用 `MIMALLOC_RESERVE_HUGE_OS_PAGES_AT=N`,其中 `N` 是 numa 节点(从 0 开始),将所有 巨大页分配在特定的 numa 节点上。 将 `fork` 与大页或巨大 OS 页结合使用时要小心:在 fork 时,OS 对原始进程中的所有页面(包括巨大 OS 页)使用写时复制。 现在当该区域中的任何内存被写入时,OS 将复制整个 1GiB 巨大页(或 2MiB 大页),这可能导致内存使用量大幅增加。 ## 安全模式 可以使用 `cmake` 中的 `-DMI_SECURE=ON` 标志在安全模式下构建 _mimalloc_。此构建启用了各种缓解措施 使 mimalloc 对漏洞利用更加强健。特别是: - 所有内部 mimalloc 页面都被保护页包围,堆元数据也位于保护页之后(因此缓冲区溢出漏洞利用无法到达元数据)。 - 所有空闲列表指针都使用每页密钥进行[编码](https://github.com/microsoft/mimalloc/blob/783e3377f79ee82af43a0793910a9f2d01ac7863/include/mimalloc-internal.h#L396),这既用于防止使用已知指针覆盖,也用于检测堆损坏。 - 检测到双重释放(并被忽略)。 - 空闲列表以随机顺序初始化,分配在页面内的扩展和重用之间随机选择,以缓解依赖于可预测分配顺序的攻击。同样,mimalloc 从 OS 分配的较大堆块也是地址随机化的。 一如既往,作为整体安全策略的一部分,请谨慎评估,因为以上所有都是缓解措施,但并非保证。 ## 调试模式 当使用调试模式构建 _mimalloc_ 时(`-DCMAKE_BUILD_TYPE=Debug`),会在运行时执行各种检查以捕获开发错误。 - 为每个对象大小详细维护统计信息。可以在运行时使用 `MIMALLOC_SHOW_STATS=1` 显示它们。 - 所有对象在末尾都有填充以检测(字节精确)堆块溢出。 - 检测到双重释放和释放无效堆指针。 - 检测到损坏的空闲列表和某些形式的释放后使用。 ## 保护模式 可以使用 `cmake` 中的 `-DMI_GUARDED=ON` 标志在保护模式下构建 _mimalloc_。 这允许在某些对象分配之后放置 OS 保护页,以便在发生缓冲区溢出时捕获它们。 这对于在大型程序中捕获缓冲区溢出错误非常有价值。然而,这也意味着任何 使用保护页分配的对象至少需要 8 KiB 内存用于保护页及其对齐。因此,为 每个分配分配保护页在内存和具有许多系统调用的性能方面可能都太昂贵了。因此,有各种环境变量(和选项)来调整这一点: - `MIMALLOC_GUARDED_SAMPLE_RATE=N`: 将采样率设置为 `N`(默认为 4000)。此模式在每个 `N` 个合适的对象分配(每个线程)之后放置一个保护页。由于在不放置保护页的情况下,保护模式下的性能接近发布模式,因此即使在生产环境中也可以使用它来捕获潜在的缓冲区溢出错误。将采样率设置为 `1` 以保护每个对象,设置为 `0` 以根本不放置保护页。 - `MIMALLOC_GUARDED_SAMPLE_SEED=N`: 从 `N` 开始采样(默认为随机)。可用于在需要时复现缓冲区溢出。 - `MIMALLOC_GUARDED_MIN=N`, `MIMALLOC_GUARDED_MAX=N`: 考虑保护页的最小和最大_舍入_对象大小(分别为 `0` 和 `1GiB`)。如果您怀疑大小为 141 的对象发生缓冲区溢出,请将最小值和最大值设置为 `148`,并将采样率设置为 `1` 以对所有这些对象进行保护。 - `MIMALLOC_GUARDED_PRECISE=1`: 如果我们有一个大小为 13 的对象,我们通常会将其放置在保护页前面 16 字节对齐的位置。使用 `MIMALLOC_GUARDED_PRECISE` 会将其精确放置在页面之前 13 个字节处,以便甚至检测到 1 个字节的溢出。但这违反了 C/C++ 最小对齐保证,因此请谨慎使用。 # 覆盖标准 Malloc 覆盖标准 `malloc`(和 `new`)可以通过_动态_或_静态_方式完成。 ## 动态覆盖 这是覆盖标准 malloc 接口的推荐方式。 ### Linux, BSD 上的动态覆盖 在这些基于 ELF 的系统上,我们预加载 mimalloc 共享库,以便所有对标准 `malloc` 接口的调用都解析为 _mimalloc_ 库。 ``` > env LD_PRELOAD=/usr/lib/libmimalloc.so myprogram ``` 您可以设置额外的环境变量来检查 mimalloc 是否正在运行,例如: ``` > env MIMALLOC_VERBOSE=1 LD_PRELOAD=/usr/lib/libmimalloc.so myprogram ``` 或使用调试版本运行以获取详细的统计信息: ``` > env MIMALLOC_SHOW_STATS=1 LD_PRELOAD=/usr/lib/libmimalloc-debug.so myprogram ``` ### MacOS 上的动态覆盖 在 macOS 上,我们也可以预加载 mimalloc 共享库,以便所有对标准 `malloc` 接口的调用都解析为 _mimalloc_ 库。 ``` > env DYLD_INSERT_LIBRARIES=/usr/lib/libmimalloc.dylib myprogram ``` 请注意,从 [shell](https://stackoverflow.com/questions/43941322/dyld-insert-libraries-ignored-when-calling-application-through-bash) 执行此操作时,某些安全限制可能会适用。 ### Windows 上的动态覆盖 我们在 Windows 上使用单独的重定向 DLL 来覆盖 mimalloc,以便我们重定向所有通过(动态)C 运行时分配器的 malloc/free 调用,包括来自其他 DLL 或库的调用。由于它在低级别拦截所有分配调用,因此可以用于包含其他第三方组件的大型程序。 要使覆盖正常工作,有四个要求: 1. 使用作为 DLL 的 C 运行时库(使用 `/MD` 或 `/MDd` 开关)。 2. 将您的程序显式链接到 `mimalloc.dll` 的 `mimalloc.dll.lib` 导出库(必须使用 `-DMI_OVERRIDE=ON` 编译,不过这是默认值)。 为了确保 `mimalloc.dll` 在运行时实际加载,最简单的方法是在 `main` 函数中插入一些对 mimalloc API 的调用,如 `mi_version()` (或在链接器命令上使用 `/include:mi_version` 开关,或类似地,在某个源文件中使用 `#pragma comment(linker, "/include:mi_version")`)。 有关如何使用此功能的示例,请参见 `mimalloc-test-override`。 3. `mimalloc-redirect.dll` 必须在运行时放置在与主 `mimalloc.dll` 相同的目录中(因为它是该 DLL 的依赖项)。 重定向 DLL 确保所有对 C 运行时 malloc API 的调用都重定向到 mimalloc 函数(位于 `mimalloc.dll` 中)。 4. 确保 `mimalloc.dll` 在最终可执行文件的导入列表中尽可能早地出现(以便它可以拦截所有潜在的分配)。 如果需要,您可以使用 `minject -l ` 来检查这一点。 为了在 Windows 上的 C++ 中获得最佳性能,还建议覆盖 `new`/`delete` 操作(通过在项目中的单个(!)源文件中包含 [`mimalloc-new-delete.h`](include/mimalloc-new-delete.h))。 环境变量 `MIMALLOC_DISABLE_REDIRECT=1` 可用于在运行时禁用动态覆盖。使用 `MIMALLOC_VERBOSE=1` 检查 mimalloc 是否成功重定向。 对于 x64 以外的平台,您可能需要特定的[重定向 dll](bin)。 此外,我们并不总是能够重新链接可执行文件或确保 `mimalloc.dll` 在导入表中排在第一位。在这种情况下,可以使用 [`minject`](bin) 工具修补可执行文件的导入表。 ## 静态覆盖 在类 Unix 系统上,您也可以静态链接 _mimalloc_ 以覆盖标准 malloc 接口。推荐的方法是将最终程序与 _mimalloc_ 单个目标文件(`mimalloc.o`)链接。我们使用目标文件而不是库文件,因为链接器优先使用它而不是存档来解析符号。为了确保标准 malloc 接口解析为 _mimalloc_ 库,请将其链接为第一个目标文件。例如: ``` > gcc -o myprogram mimalloc.o myfile1.c ... ``` 另一种适用于所有平台的静态覆盖方法是静态链接到 mimalloc(如简介中所示),并在每个源文件中包含一个将 `malloc` 等重新定义为 `mi_malloc` 的头文件。这由 [`mimalloc-override.h`](include/mimalloc-override.h) 提供。只有当所有源代码都在您的控制之下时,这才可靠地工作,否则可能会发生来自不同堆的指针混合! # 工具 通常,我们建议将标准分配器与内存跟踪工具一起使用,但 mimalloc 也可以构建为支持 [address sanitizer][asan] 或优秀的 [Valgrind] 工具。 此外,它可以构建为支持 Windows 事件跟踪 ([ETW])。 这有一个小的性能开销,但确实允许直接在最终可执行文件上检测内存泄漏和字节精确的缓冲区溢出。另请参见 `test/test-wrong.c` 文件以使用各种工具进行测试。 ## Valgrind 要构建具有 [valgrind] 支持,请使用 `MI_TRACK_VALGRIND=ON` cmake 选项: ``` > cmake ../.. -DMI_TRACK_VALGRIND=ON ``` 这也可以与安全模式或调试模式结合使用。 然后您可以直接在 valgrind 下运行您的程序: ``` > valgrind ``` 如果您依赖 mimalloc 覆盖 `malloc`/`free`(而不是直接使用 `mi_malloc`/`mi_free` API),您还需要告诉 `valgrind` 不要自己拦截这些调用,并使用: ``` > MIMALLOC_SHOW_STATS=1 valgrind --soname-synonyms=somalloc=*mimalloc* -- ``` 通过设置 `MIMALLOC_SHOW_STATS` 环境变量,您可以检查确实使用的是 mimalloc 而不是标准分配器。尽管 [Valgrind 选项][valgrind-soname] 称为 `--soname-synonyms`,但这在使用静态库或目标文件覆盖时也适用。 要将动态覆盖 mimalloc 与 `valgrind` 一起使用,请使用: ``` > valgrind --trace-children=yes --soname-synonyms=somalloc=*mimalloc* /usr/bin/env LD_PRELOAD=/usr/lib/libmimalloc.so -- ``` 另请参见 `test/test-wrong.c` 文件以使用 `valgrind` 进行测试。 Valgrind 支持处于初始开发阶段——请报告任何问题。 ## ASAN 要构建具有地址清理器,请使用 `-DMI_TRACK_ASAN=ON` cmake 选项: ``` > cmake ../.. -DMI_TRACK_ASAN=ON ``` 这也可以与安全模式或调试模式结合使用。 然后您可以这样运行您的程序:' ``` > ASAN_OPTIONS=verbosity=1 ``` 当您将程序与 mimalloc 的地址清理器构建链接时,您通常也应该在编译该程序时启用地址清理器。 例如,假设您在 `out/debug` 中构建 mimalloc: ``` clang -g -o test-wrong -Iinclude test/test-wrong.c out/debug/libmimalloc-asan-debug.a -lpthread -fsanitize=address -fsanitize-recover=address ``` 由于地址清理器重定向标准分配函数,在某些平台上(例如 macOSX),需要使用 `-DMI_OVERRIDE=OFF` 编译 mimalloc。 地址清理器支持处于初始开发阶段——请报告任何问题。 ## ETW Windows 的事件跟踪 ([ETW]) 提供了一种高性能的方式来捕获通过 mimalloc 进行的所有分配并稍后分析它们。要构建具有 ETW 支持,请使用 `-DMI_TRACK_ETW=ON` cmake 选项。 然后,您可以使用 Windows 性能记录器 (WPR) 使用 `src/prim/windows/etw-mimalloc.wprp` 配置文件捕获分配跟踪。在管理提示符中,您可以使用: ``` > wpr -start src\prim\windows\etw-mimalloc.wprp -filemode > > wpr -stop .etl ``` 然后在 Windows 性能分析器 (WPA) 中打开 `.etl`,或使用像 [TraceControl] 这样专门用于分析 mimalloc 跟踪的工具。 # 性能 最后更新:2021-01-30 我们在广泛的基准测试范围内,将 _mimalloc_ 与许多其他顶级分配器进行了测试,范围从各种现实世界程序到合成基准测试,以查看分配器在更极端情况下的行为。在我们的基准测试套件中,_mimalloc_ 优于其他领先的分配器(_jemalloc_, _tcmalloc_, _Hoard_ 等),并且具有类似的内存占用。一个很好的特性是它在广泛的基准测试范围内表现始终如一。 通用内存分配器很有趣,因为不存在最优的算法——对于一个给定的分配器,通常可以构造一个它表现不佳的工作负载。因此,目标是找到一种在广泛的基准测试范围内表现良好的分配策略,而不会在不太常见的情况下遭受(太多)性能不佳的影响。 一如既往,请谨慎解释这些结果,因为某些基准测试测试的是可能永远不适用于您的工作负载的合成或不常见情况。例如,大多数分配器在 `xmalloc-testN` 上表现不佳,但这甚至包括世界上一些最大系统(如 Chrome 或 FreeBSD)中使用的最好的工业分配器,如 _jemalloc_ 和 _tcmalloc_。 此外,这里的基准测试不衡量非常大和长时间运行的服务器工作负载或分配的最坏情况延迟的行为。`mimalloc` 在此类工作负载上运行良好做了很多工作(例如,减少长时间运行服务上的虚拟内存碎片),但此类优化并不总是反映在当前的基准测试套件中。 我们这里仅展示一个概述——有关更多具体细节和进一步的基准测试,我们参考[技术报告](https://www.microsoft.com/en-us/research/publication/mimalloc-free-list-sharding-in-action)。 基准测试套件是自动化的,并作为 [mimalloc-bench](https://github.com/daanx/mimalloc-bench) 单独提供。 ## 16 核 AMD 5950x (Zen3) 上的基准测试结果 在 16 核 AMD 5950x 处理器(3.4Ghz,4.9Ghz 加速)上测试,具有 32GiB 内存(3600Mhz),运行 Ubuntu 20.04,glibc 2.31 和 GCC 9.3.0。 我们测量 _mimalloc_ 的三个版本:主版本 `mi` (tag:v1.7.0),新的 v2.0 beta 版本作为 `xmi` (tag:v2.0.0),以及安全模式下的主版本作为 `smi` (tag:v1.7.0)。 其他分配器是 用于 Chrome 的 Google [_tcmalloc_](https://github.com/gperftools/gperftools) (`tc`, tag:gperftools-2.8.1), 由 Jason Evans 开发并用于 Firefox 和 FreeBSD 的 Facebook [_jemalloc_](https://github.com/jemalloc/jemalloc) (`je`, tag:5.2.1), Intel 线程构建块[分配器](https://github.com/intel/tbb) (`tbb`, tag:v2020.3), Mattias Jansson 的 [rpmalloc](https://github.com/mjansson/rpmalloc) (`rp`,tag:1.4.1), Emery Berger \[1] 的原始可扩展 [_Hoard_](https://github.com/emeryberger/Hoard) (git:d880f72) 分配器, Bobby Powers _et al_ \[8] 的内存压缩 [_Mesh_](https://github.com/plasma-umass/Mesh) (git:67ff31a) 分配器, 最后是默认系统分配器 (`glibc`, 2.31)(基于 _PtMalloc2_)。 任何以 `N` 结尾的基准测试都在所有 32 个逻辑核上并行运行。 结果取 10 次运行的平均值,并相对于 mimalloc 报告(其中 1.2 表示运行时间增加了 1.2×)。 图例还包含分配器之间的_总体相对得分_,如果分配器在所有基准测试中都是最快的,则最高为 100 分。 Dave Barrett 的单线程 _cfrac_ 基准测试是连续分数分解的实现,它使用许多小的短期分配。 所有分配器在此类常见用法上都表现良好,其中 _mimalloc_ 仅比 _tcmalloc_ 和 _jemalloc_ 快一点。 _leanN_ 程序很有趣,因为它是 [Lean](https://github.com/leanprover/lean) 定理证明器编译其自己标准库的大型现实并发工作负载,与 _tcmalloc_ 相比有 13% 的加速。这非常显著:如果 Lean 在分配器上花费 20% 的时间,这意味着 _mimalloc_ 在这里比 _tcmalloc_ 快 1.6×。(这令人惊讶,因为这并不是在像 _alloc-test_ 这样的纯分配基准测试中衡量的。我们推测我们在这里看到这种巨大的改进是因为 _mimalloc_ 在分配中具有更好的局部性,这也提高了程序中*其他*计算的性能)。 单线程 _redis_ 基准测试再次表明,大多数分配器在此类工作负载上表现良好。 Larson 和 Krishnan \[2] 的 _larsonN_ 服务器基准测试在线程之间分配和释放。他们在实际服务器应用程序中观察到了这种行为(他们称之为_出血_),该基准测试模拟了这一点。 在这里,_mimalloc_ 比 _tcmalloc_ 和 _jemalloc_ 快相当多,可能是由于不同线程之间的对象迁移。 _mstressN_ 工作负载执行许多分配和重新分配,并在线程之间迁移对象(如 _larsonN_)。但是,它还创建和销毁 _N_ 个工作线程几次,使一些对象在分配线程的生命周期之后仍然保持活动状态。我们在许多大型服务器应用程序中观察到了这种行为。 Mattias Jansson 的 [_rptestN_](https://github.com/mjansson/rpmalloc-benchmark) 基准测试是最初为 _rpmalloc_ 设计的分配器测试,并试图模拟多个线程上的现实分配模式。在这里,分配器之间的差异变得更加明显。 第二组基准测试测试分配器的特定方面,并显示它们之间更极端的差异。 [OLogN Technologies AG](http://ithare.com/testing-memory-allocators-ptmalloc2-tcmalloc-hoard-jemalloc-while-trying-to-simulate-real-world-loads/) 的 _alloc-test_ 是一个非常分配密集的基准测试,在各种大小类中执行数百万次分配。该测试经过缩放,以便当分配器在 _alloc-test1_ 和 _alloc-testN_ 上的表现几乎相同时,这意味着它线性扩展。 _sh6bench_ 和 _sh8bench_ 基准测试由 [MicroQuill](http://www.microquill.com/) 作为 SmartHeap 的一部分开发。 在 _sh6bench_ 中,_mimalloc_ 比其他分配器做得更 好(比 _jemalloc_ 快 2.5× 以上)。 我们无法很好地解释这一点,但相信它 部分是由 _sh6bench_ 中的“反向”释放模式引起的。 _sh8bench_ 是具有线程间对象迁移的变体;而 _tcmalloc_ 在 _sh6bench_ 上表现良好,添加对象迁移导致它比以前慢 10×。 Lever 和 Boreham \[5] 以及 Christian Eder 的 _xmalloc-testN_ 基准测试模拟不对称工作负载,其中一些线程只分配,而另一些只释放——他们在较大的服务器应用程序中观察到了这种模式。在这里,我们看到 _mimalloc_ 的无争用分片线程空闲列表技术得到了回报,因为它以非常大的优势优于其他分配器。只有 _rpmalloc_, _tbb_, 和 _glibc_ 在此基准测试上也能很好地扩展。 Emery Berger \[1] 的 _cache-scratch_ 基准测试,随 Hoard 分配器引入,用于测试缓存行的_被动伪_共享。 使用单线程时,它们都 执行相同,但是当使用多个线程运行时,潜在的分配器引起的缓存行的伪共享会导致较大的运行时差异。 Crundal \[6] 详细描述了为什么 _tcmalloc_ 设计中会发生伪缓存行共享,并讨论了如何通过一些小的实现更改来避免这种情况。 只有 _tbb_, _rpmalloc_ 和 _mesh_ 分配器也完全避免了缓存行共享,而 _Hoard_ 和 _glibc_ 似乎缓解了影响。Kukanov 和 Voss \[7] 详细描述了 _tbb_ 的设计如何避免伪缓存行共享。 ## 在 36 核 Intel Xeon 上 为了完整性,是在大型 Amazon [c5.18xlarge](https://aws.amazon.com/ec2/instance-types/#Compute_Optimized) 实例上的结果,由 2×18 核 Intel Xeon (Cascade Lake) 组成,3.4GHz (加速 3.5GHz),具有 144GiB ECC 内存,运行 Ubuntu 20.04,glibc 2.31,GCC 9.3.0,和 Clang 10.0.0。这一次,mimalloc 分配器(mi, xmi, 和 smi)是使用 Clang 编译器而不是 GCC 编译的。 结果与 AMD 结果相似,但有趣的是看到 _larsonN_, _mstressN_, 和 _xmalloc-testN_ 基准测试中的差异。 ## 峰值工作集 下图显示了基准测试上分配器的峰值工作集(rss)(在 c5.18xlarge 实例上)。 请注意,_xmalloc-testN_ 内存使用量应忽略不计,因为它分配得越快,程序运行得越快。同样,_larsonN_, _mstressN_, _rptestN_ 和 _sh8bench_ 的内存使用量可能因调度和速度而异。尽管如此,我们希望改善 _mstressN_ 和 _rptestN_ 上的内存使用(就像 _cfrac_, _larsonN_ 和 _sh8bench_ 具有扭曲结果的小工作集一样)。