perf(membuddy): 单线程默认关锁并内联热路径常量/移位,消除原子与重复调用

新增编译期开关 MEMBUDDY_THREAD_SAFE(默认 0=单线程):_lock/_unlock 常量折叠为空体,消除每次分配的原子 xchg/store;多线程置 1 行为逐字节不变。

_canary_pattern 折叠为 MEMBUDDY_CANARY_PATTERN 并在调用点内联(值逐位等于原 (lo<<32)|lo);_buddy_of/_split_to_order/alloc 内联 MEMBUDDY_MIN_BLOCK<<order;_coalesce 增加本阶空闲链表为空即 break 的等价提前退出。
This commit is contained in:
TermiNexus committed 2026-09-13 21:11:33 +08:00
1 parent ed67ecc656
commit 04a49ae612
1 file changed
+54 -13
+54 -13
View File
@@ -67,11 +67,31 @@ MEMBUDDY_DBG: t.CDefine = 1
# memhub._memdbg_enable = 0
_memdbg_enable: int = MEMBUDDY_DBG
# [锁] 线程安全编译期开关:默认 0 = 单线程(关闭自旋锁,_lock/_unlock 为空体)。
# 自举编译等单线程场景下每次 alloc 省去一次原子 xchg + 一次原子 store
# (实测 _lock/_unlock/__atomic_test_and_set/__atomic_clear 各约 2026 万次,
# 且在单线程下为纯开销)。置 1 = 多线程(保留原有原子自旋加解锁)。
# 选编译期(t.CDefine 常量)而非运行期开关的理由:常量比较在 HandleIf 处被
# 常量折叠,未取分支整体不生成代码 ⇒ _lock/_unlock 退化为空函数,真正消除
# 原子开销;运行期开关仍需每次分配做一次全局读 + 分支。既有 _memdbg_enable
# 用运行期是因为它要支持"同一次运行内切换"(负向用例),而线程模型在编译期
# 即已固定。多线程使用 MemBuddy 必须置 1(语义与历史实现逐字节一致)。
MEMBUDDY_THREAD_SAFE: t.CDefine = 0
# 块尾 canary 常量(0xA5A5A5A5A5A5A5A5,64 位)——热点优化(实测):原
# _canary_pattern() 函数被调用约 2658 万次(每次 alloc 写块尾 + check_alive_blocks
# 校验),改为 t.CDefine 后调用点直接内联为立即数,消除全部函数调用。
# 值构造:lo = 0xA5A5A5A5(低 32 位,其高 32 位为 0);(lo << 32) 恰好填满高 32 位,
# 再 | lo 填低 32 位 ⇒ 0xA5A5A5A5A5A5A5A5,与原函数 (lo << 32) | lo 逐位相同。
# 注意:不写十进制大字面量(11936128518282651045)——本仓库踩过"大十进制字面量
# 被解析截断"的坑;此处沿用原函数的移位组合,由 ConstEvaluator 求值(值
# >0x7FFFFFFF 时 _make_define_constant 升 i64,与原函数返回的 t.CUInt64T 同位宽)。
MEMBUDDY_CANARY_PATTERN: t.CDefine = (0xA5A5A5A5 << 32) | 0xA5A5A5A5
def _canary_pattern() -> t.CUInt64T:
"""块尾 canary 值(0xA5A5A5A5A5A5A5A5,64 位)"""
lo: t.CUInt64T = t.CUInt64T(0xA5A5A5A5)
return (lo << 32) | lo
"""块尾 canary 值(0xA5A5A5A5A5A5A5A5,64 位)——统一引用编译期常量"""
return t.CUInt64T(MEMBUDDY_CANARY_PATTERN)
def _align_up(val: t.CSizeT, align: t.CSizeT) -> t.CSizeT:
if align == 0: return val
@@ -322,7 +342,9 @@ class MemSlab(MemManager):
# ============================================================
# MemBuddy - Buddy system allocator
# Free list head array stored at arena start; subsequent memory managed in power-of-two sized blocks
# Thread-safe (spinlock enabled), supports block coalescing
# Thread-safety is compile-time gated by MEMBUDDY_THREAD_SAFE:0=单线程默认(关闭自旋锁,
# 消除每次分配一次的原子加解锁开销);多线程使用必须置 1(保留原子自旋)。
# supports block coalescing
#
# Checkpoint 支持(供编译器等长任务回收阶段性分配):
# mark() → 返回当前分配水位(不分配内存)
@@ -562,7 +584,10 @@ class MemBuddy(MemManager):
def _buddy_of(self, block: t.CVoid | t.CPtr, order: t.CInt) -> t.CVoid | t.CPtr:
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
bs: t.CSizeT = _block_size_at_order(order)
# order 恒 ≥ 0(调用链:_coalesce 的 order 源自合法 header (stored>>1) 且只递增,
# 起点为已释放块的阶)。内联左移等价于 _block_size_at_order(order),省去每次
# 调用的 call/ret(_buddy_of 实测 3517 万次,为 _block_size_at_order 主要来源)。
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK << t.CSizeT(order)
buddy_offset: t.CSizeT = offset ^ bs
return t.CVoid(t.CUInt64T(self.usable) + buddy_offset, t.CPtr)
@@ -585,13 +610,22 @@ class MemBuddy(MemManager):
block: t.CVoid | t.CPtr = self._fl_pop(found_order)
while found_order > to_order:
found_order -= 1
bs: t.CSizeT = _block_size_at_order(found_order)
# 循环条件保证 found_order > to_order ≥ 0,故递减后 found_order ≥ 0;
# 内联左移等价于 _block_size_at_order(found_order),省去每次拆分的 call/ret。
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK << t.CSizeT(found_order)
buddy: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(block) + bs, t.CPtr)
self._fl_push(found_order, buddy)
return block
def _coalesce(self, block: t.CVoid | t.CPtr, order: t.CInt):
while order < self.max_order:
# 提前退出(不改变语义,仅省去"注定找不到"的两次调用):本阶空闲链表
# 为空 ⇒ _fl_find_and_remove 入口即判 head==0 返回 0 ⇒ 本阶不可能合并。
# 这里先判空,可省去本轮 _buddy_of(内部含 _block_size_at_order)与
# _fl_find_and_remove 两次调用;判据与 _fl_find_and_remove 的入口条件
# 逐字相同,且两函数均无副作用,故对空闲链表语义与顺序完全等价。
if self.free_lists[order] == 0:
break
buddy: t.CVoid | t.CPtr = self._buddy_of(block, order)
found: t.CInt = self._fl_find_and_remove(order, buddy)
if found == 0:
@@ -638,11 +672,15 @@ class MemBuddy(MemManager):
# === Spinlock ===
def _lock(self): # can use the spinlock library for decoupling
while atom.__atomic_test_and_set(c.Addr(self.lock_val), atom.ATOMIC_ACQUIRE):
pass
# 单线程编译(MEMBUDDY_THREAD_SAFE=0)下本 if 被常量折叠,函数体为空;
# 多线程(=1)下保留原有原子自旋加锁,行为与历史实现逐字节一致。
if MEMBUDDY_THREAD_SAFE != 0:
while atom.__atomic_test_and_set(c.Addr(self.lock_val), atom.ATOMIC_ACQUIRE):
pass
def _unlock(self):
atom.__atomic_clear(c.Addr(self.lock_val), atom.ATOMIC_RELEASE)
if MEMBUDDY_THREAD_SAFE != 0:
atom.__atomic_clear(c.Addr(self.lock_val), atom.ATOMIC_RELEASE)
# === Virtual function overrides ===
@@ -662,9 +700,11 @@ class MemBuddy(MemManager):
c.DerefAs(block, t.CVoid(t.CUInt64T((order << 1) | 1), t.CPtr))
result = t.CVoid(t.CUInt64T(block) + MEMBUDDY_HEADER_SIZE, t.CPtr)
# 写块尾 canary(block + block_size - CANARY)
bs: t.CSizeT = _block_size_at_order(order)
# order 恒 ≥ 0(_order_for_size 返回值),内联左移等价于
# _block_size_at_order(order),省去每次 alloc 的 call/ret。
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK << t.CSizeT(order)
c.DerefAs(t.CVoid(t.CUInt64T(block) + bs - MEMBUDDY_CANARY, t.CPtr),
t.CVoid(_canary_pattern(), t.CPtr))
t.CVoid(MEMBUDDY_CANARY_PATTERN, t.CPtr))
# 分配守卫:填充用户数据区 0xCD 哨兵(未初始化检测)。
# 调用方初始化(list.__init__ 等)覆盖前,字段保持 0xCDCD...
# CTORDBG 打印 d0-d40 时:0xCDCD...=未初始化,其他=被覆盖。
@@ -1167,11 +1207,12 @@ class MemBuddy(MemManager):
if offset % bs != 0:
return 5
# 块尾 canary 校验:越界写穿数据区会破坏块尾哨兵,
# 立即定位到越界写发生的文件(崩溃点漂移的关键哨兵)
# 立即定位到越界写发生的文件(崩溃点漂移的关键哨兵)。
# 与 alloc 写入点统一使用编译期常量 MEMBUDDY_CANARY_PATTERN(同值同宽)。
can_stored: t.CVoid | t.CPtr = t.CVoid(
c.Deref(t.CUInt64T(t.CUInt64T(block) + bs - MEMBUDDY_CANARY, t.CPtr), t.CPtr),
t.CPtr)
if t.CUInt64T(can_stored) != _canary_pattern():
if t.CUInt64T(can_stored) != t.CUInt64T(MEMBUDDY_CANARY_PATTERN):
return 6
ci += 1
return 0