mimalloc Windows 原语与 ETW 分配跟踪:codebase-memory-mcp 内存层的系统级剖析
codebase-memory-mcp 的生产构建直接以 mimalloc 作为进程级内存分配器(见 src/foundation/mem.c),而 mimalloc 在 Windows 上的行为由 vendored 源码目录 vendored/mimalloc/src/prim/windows 中的"原语层"(prim)决定。本文以 vendored/mimalloc/src/prim/windows/readme.md 为纲,讲清两件事:该目录中 prim.c 如何封装 Windows OS 分配原语(VirtualAlloc 体系、大页/巨页、NUMA 感知、进程/线程生命周期钩子),以及 etw.man → etw.h 的 Event Tracing for Windows(ETW)管线如何让你用 wpr 抓取 mimalloc 的逐次分配/释放事件并在 Windows Performance Analyzer(WPA)中分析。
一、目录定位:readme 声明的两个组件
readme 原文对目录职责的划分只有两条,但可以精确映射到实际文件:
prim.c包含 Windows 平台的 OS 分配原语——即 mimalloc 抽象层(_mi_prim_*接口)在 Windows 上的全部实现;etw.h由etw.man生成,manifest 中定义了 mimalloc 的两个事件:100 表示分配(allocation),101 表示释放(free);配套的etw-mimalloc.wprp是 Windows Performance Recorder 的采集 profile。
对应文件清单:prim.c、etw.man、etw.h、etw-mimalloc.wprp。
需要注意的前提:prim.c 文件头注释写明 "This file is included in src/prim/prim.c"(见 prim.c),即它不是独立编译单元,而是由 mimalloc 的平台分发文件在 Windows 构建时 include 进来,通过条件编译参与链接。
二、OS 分配原语:prim.c 的核心机制
2.1 初始化:动态绑定 Windows API 与系统参数
入口是 _mi_prim_mem_init(prim.c#L144-L214),它完成三组工作:
- 读取系统内存参数:
GetSystemInfo取页大小(dwPageSize)与分配粒度(dwAllocationGranularity,代码中默认按 64 KiB 处理,见 prim.c#L142),并从lpMaximumApplicationAddress推算用户态虚拟地址位数; - 动态绑定新式 API:
VirtualAlloc2仅在 Windows 10 / Server 2016 起提供,为了兼容老系统,prim.c通过LoadLibrary/GetProcAddress动态查找,并优先取VirtualAlloc2FromApp(Windows Store 应用可用,见 prim.c#L164-L177)。同理还绑定了NtAllocateVirtualMemoryEx(1 GiB 巨页)、Windows 7+ 的 NUMA 扩展 API 与GetLargePageMinimum; - 物理内存探测:动态调用
GetPhysicallyInstalledSystemMemory填充config->physical_memory_in_kib,并读取GetVersionExW缓存主/次版本号,供后续线程池判断使用。
代码中还自行定义了 MI_MEM_EXTENDED_PARAMETER / MI_MEM_ADDRESS_REQUIREMENTS 等最小结构(prim.c#L36-L55),注释说明这是为了"能在旧版 SDK 上编译"——这是理解该文件大量手工函数指针声明的原因。
2.2 分配路径:VirtualAlloc 三级回退与 OOM 重试
_mi_prim_alloc(prim.c#L349-L358)按 commit 标志组合 MEM_RESERVE/MEM_COMMIT 后进入 win_virtual_alloc,后者是三级策略:
- 大页(2 MiB)优先:当
mi_option_allow_large_os_pages开启且是"reserve+commit"分配时,带MEM_LARGE_PAGES重试。源码里有个值得注意的熔断器large_page_try_ok(prim.c#L317-L338):一旦大页分配失败,计数器置 10,此后 10 次分配不再尝试大页——注释解释原因是"大页失败后 VirtualAlloc 调用会变得非常昂贵"; - 对齐分配:
win_virtual_alloc_prim_once(prim.c#L246-L273)在 64 位下先尝试 2 TiB 之上的虚拟地址区间提示(配合 mimalloc 自身的对齐 hint),对齐要求超过分配粒度时改用VirtualAlloc2+MiMemExtendedParameterAddressRequirements; - 最后回退到普通
VirtualAlloc。
外层 win_virtual_alloc_prim(prim.c#L287-L313)实现了 mi_option_retry_on_oom 选项:对 OOM 类错误码(ERROR_COMMITMENT_MINIMUM、ERROR_COMMITMENT_LIMIT、ERROR_PAGEFILE_QUOTA、ERROR_NOT_ENOUGH_MEMORY)在最多 10 次、总时长约 2.2 秒内做递增等待重试,这是针对 issue #894 的"先坚持一下,内存也许就腾出来了"策略。
大页的使能本身也有 Windows 特有门槛:win_enable_large_os_pages_once(prim.c#L95-L128)必须成功 OpenProcessToken + AdjustTokenPrivileges 激活 SeLockMemoryPrivilege(组策略中的"锁定内存页"权限),失败时只打印警告并退回普通页。
2.3 提交/取消提交/重置与释放
_mi_prim_commit用VirtualAlloc(addr, size, MEM_COMMIT, PAGE_READWRITE)(prim.c#L368-L383);_mi_prim_decommit用VirtualFree(..., MEM_DECOMMIT),且保守地总是置*needs_recommit = true(prim.c#L385-L389);_mi_prim_reset用MEM_RESET(prim.c#L391-L400),对应 mimalloc 的"快速回收页"语义;_mi_prim_free(prim.c#L221-L239)处理了一个 Windows 特有的边角:若VirtualFree报ERROR_INVALID_ADDRESS(回退路径返回了对齐后的内部指针而非区域基址),先用VirtualQuery找AllocationBase,只要偏移小于 4 MiB 就用基址再释放一次。
2.4 巨页(1 GiB)与 NUMA
_mi_prim_alloc_huge_os_pagesx(prim.c#L418-L457)体现了完整的回退链:先尝试 NtAllocateVirtualMemoryEx + MI_MEM_EXTENDED_PARAMETER_NONPAGED_HUGE 属性申请 1 GiB 大页(可叠加 MiMemExtendedParameterNumaNode 指定 NUMA 节点),失败则置原子标志 mi_huge_pages_available = 0 永久放弃巨页,降级为 2 MiB 大页;再不行则用 VirtualAlloc2 做 NUMA 感知分配,最旧系统退回普通 VirtualAlloc。
NUMA 拓扑查询(_mi_prim_numa_node / _mi_prim_numa_node_count,prim.c#L470-L517)同样双轨制:优先 Win7+ 扩展 API(支持 >64 核,修复 issue #277),否则回退到受限的旧 API;节点计数时还会逐个向下检查"最高号节点是否真的挂了处理器"(issue #282)。
2.5 进程/线程生命周期:TLS + CRT 组合钩子
Windows 上自动初始化 mimalloc 的时机问题由 mi_win_main 与一组可切换策略解决(prim.c#L719-L745)。默认策略 MI_WIN_INIT_USE_CRT_TLS 是"组合拳":
- 通过 PE 的 TLS 回调段(
.CRT$XLB/.CRT$XLY中的_mi_tls_callback_pre/post,32/64 位分别用data_seg/const_seg宏,见 prim.c#L811-L854)在构造函数之前 attach、析构之后 detach; - 对 DLL 场景额外
atexit(&mi_crt_done),保证 DLL 中 CRT 结束发生在 TLS detach 之后(见mi_crt_init注释,prim.c#L775-L782); - 另有
MI_WIN_INIT_USE_RAW_DLLMAIN(通过_pRawDllMain钩子,注释指出会与 Boost 等同样钩子此指针的库冲突)以及更旧的 DllMain/TLS/FLS 选项。
mi_win_main 本身按事件分发:DLL_PROCESS_ATTACH 触发 _mi_auto_process_init(),DLL_PROCESS_DETACH 触发 _mi_auto_process_done(),DLL_THREAD_DETACH(未重定向时)触发 _mi_thread_done(NULL)(prim.c#L719-L731)。
2.6 其他平台原语速览
同一文件还提供:基于 QueryPerformanceCounter 的毫秒时钟(prim.c#L524-L539);进程内存信息 _mi_prim_process_info 动态加载 psapi.dll 取工作集/提交量/缺页计数(prim.c#L559-L588)——主项目 src/foundation/diagnostics.c 正是围绕 mi_process_info 的字段做跨平台校正的;线程池检测通过读 TEB 偏移 0x1778(64 位)判断 pool_data(prim.c#L693-L704);随机数优先 bcrypt.dll 的 BCryptGenRandom,可用 MI_USE_RTLGENRANDOM 切换到 RtlGenRandom(注释记录了 C++ 下抛异常与 VS 调试器死锁的历史问题);stderr 输出则在控制台/重定向两种情况下分别走 WriteConsoleA/WriteFile,避免 C 运行时在主线程退出后无法处理 locale 相关输出(prim.c#L594-L627)。
三、ETW 管线:从 manifest 到 WPA
3.1 事件定义与生成物
etw.man 是 ETW provider 的 XML manifest。文件首行注释给出了生成命令(在 Visual Studio 命令提示符下运行 mc .\etw.man,见 etw.man),生成的 etw.h 是典型的 Message Compiler 输出。从生成物可以读出全部事实:
- Provider 名为
microsoft-windows-mimalloc,GUID 为138f4dbb-ee04-4899-aa0a-572ad4475779(etw.h#L666); - 两个事件描述符:
ETW_MI_ALLOC = 0x64(即十进制 100)、ETW_MI_FREE = 0x65(即十进制 101),与 readme 的"100 is an allocation, 101 is for a free"逐字吻合(etw.h#L675-L678); - 两个事件共用同一个双
unsigned __int64模板(地址 + 大小),写入宏为EventWriteETW_MI_ALLOC(Address, Size)/EventWriteETW_MI_FREE(Address, Size)(etw.h#L824-L859)。
这些宏如何接到 mimalloc 分配路径上,由 include/mimalloc/track.h 完成映射:
#define mi_track_init() EventRegistermicrosoft_windows_mimalloc()
#define mi_track_done() EventUnregistermicrosoft_windows_mimalloc()
#define mi_track_malloc_size(p,reqsize,size,zero) EventWriteETW_MI_ALLOC((UINT64)(p), size)
#define mi_track_free_size(p,size) EventWriteETW_MI_FREE((UINT64)(p), size)
调用点在分配快路径上,例如 src/alloc.c#L150 与 src/alloc-aligned.c#L223 的 mi_track_malloc。从源码结构看,该管线以 MI_TRACK_* 编译开关启用,且由于 ETW 写入宏在事件未启用时只是位测试后直接短路(MCGEN_EVENT_ENABLED 检查,见 etw.h#L236-L238),非跟踪构建下的开销被设计为接近零。
3.2 采集 profile:etw-mimalloc.wprp 的内容
readme 说该 profile 面向 WPR,实际文件是一个 XML profile,核心配置为:
Mimalloc_Collector事件采集器:BufferSize=1024、Buffers=100;EventProvider Name="138f4dbb-ee04-4899-aa0a-572ad4475779",NonPagedMemory="true" Stack="true"——即开启调用栈采集、使用非分页内存缓冲,适合生产级抓取;EventFilters FilterIn="true"只放行EventId 100与EventId 101,精确对应分配/释放两个事件;- 系统采集器附带
Loader关键字,Profile 命名CustomHeap.Verbose.File,日志模式为 File,便于事后导入 WPA。
3.3 实操:wpr 抓取与 WPA 分析
按 readme 给出的流程(需管理员权限的提示符):
> wpr -start src\prim\windows\etw-mimalloc.wprp -filemode
> <my mimalloc program>
> wpr -stop test.etl
然后打开 test.etl 到 Windows Performance Analyzer 中分析。对应到本仓库的上下文:如果你的 mimalloc 程序是链接了 vendored mimalloc 的可执行文件(或生产构建的 codebase-memory-mcp 相关 Windows 程序,其分配器行为即由 prim.c 决定),test.etl 中将出现每个 EventId=100/101 的分配/释放事件,携带地址、大小与调用栈,可直接在 WPA 中做堆行为的时间线分析。注意 readme 中路径是相对于 mimalloc 源码树写作的 src\prim\windows\...,在本仓库中实际文件位置为 etw-mimalloc.wprp,使用时请替换为实际路径。
四、小结:这份 prim 层对使用方意味着什么
- 分配语义:mimalloc 在 Windows 上是 reserve/commit 两段的 VirtualAlloc 模型,
MEM_RESET快速回收、MEM_DECOMMIT归还物理页,大页/巨页按"能力探测 + 熔断"策略渐进启用; - 兼容策略:所有 Win10+ 新 API 均运行时动态绑定,老系统自动降级——这正是它能在 CI 矩阵覆盖旧 Windows 的原因;
- 可观测性:ETW provider 是 mimalloc 的"官方"分配级观测通道,
100/101两个事件加 WPR profile 构成完整的抓取-分析闭环,且事件未启用时对热路径几乎零成本; - 对本项目的意义:codebase-memory-mcp 通过 src/foundation/mem.c 统一管理 mimalloc 的配置(提交策略、page reclaim 等),而 Windows 构建下这些策略最终都落到本文分析的 prim.c 原语实现上。
若要进一步验证,可从三个入口切入:跟踪宏映射 track.h、ETW 生成头 etw.h 中 EventWriteETW_MI_ALLOC 的定义,以及 src/foundation/mem.c 中主项目对 mimalloc 选项的调优注释。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00