perf(memhub): 分配记录表改为仅 --detection 使用,free 不再线性扫描全部历史分配
记录区此前与 detection 解耦、每次 alloc 都写一条,_det_count 对永不 reset 的 PermPool 无上限增长;free 归还时按 _det_count 线性查找记录并置空闲位,于是每次 free 都是 O(历史分配数)。改为 detection==0 时不分配记录区也不记录/不回填,free 变 O(1);OOM 报告的块序分布走 chk_stack 不受影响。
This commit is contained in:
1 parent
5c0a454861
commit
963dfc5a3c
1 file changed
+33
-25
@@ -765,16 +765,16 @@ class MemBuddy(MemManager):
|
||||
rem -= blk
|
||||
self.usable_size = used_area
|
||||
|
||||
# Allocate the record area separately with stdlib.malloc (not from the buddy area, same
|
||||
# policy as chk_stack). Capacity = detection_size // 16 (each entry {ptr, size}).
|
||||
# Records + overlap detection are decoupled from detection: even without it the record area
|
||||
# is allocated and same-address duplicate-allocation detection is on (no markers, no layout
|
||||
# change), which locates free-list / duplicate-allocation corruption.
|
||||
det_arena: rawptr = stdlib.malloc(self.detection_size)
|
||||
if det_arena is not None:
|
||||
self._det_arena = det_arena
|
||||
self._det_records = uint64*(det_arena)
|
||||
self._det_cap = self.detection_size // 16
|
||||
# Record area: allocated only in detection mode. Recording every alloc in production was
|
||||
# 16 B per allocation (growing by realloc to hundreds of MB under churn) and made free's
|
||||
# record-marking loop a linear scan of every allocation ever made -- unbounded for a pool
|
||||
# that never resets (PermPool). All consumers already guard on `_det_records is not None`.
|
||||
if self.detection != 0:
|
||||
det_arena: rawptr = stdlib.malloc(self.detection_size)
|
||||
if det_arena is not None:
|
||||
self._det_arena = det_arena
|
||||
self._det_records = uint64*(det_arena)
|
||||
self._det_cap = self.detection_size // 16
|
||||
self._det_count = 0
|
||||
|
||||
# === Checkpoint support ===
|
||||
@@ -1602,18 +1602,24 @@ class MemBuddy(MemManager):
|
||||
stdio.fflush(None)
|
||||
stdlib.exit(1)
|
||||
dbg_cur = rawptr(*((uint64*)(dbg_cur)))
|
||||
if self._det_count >= self._det_cap:
|
||||
# Record table full: double it so is_alive/_det_probe never miss live blocks.
|
||||
new_cap: sizet = self._det_cap * 2
|
||||
new_rec: rawptr = rawptr(stdlib.realloc(self._det_arena, new_cap * 16))
|
||||
if new_rec is not None:
|
||||
self._det_arena = new_rec
|
||||
self._det_records = (uint64*)(new_rec)
|
||||
self._det_cap = new_cap
|
||||
if self._det_count < self._det_cap:
|
||||
self._det_records[self._det_count * 2] = rec_ptr_d
|
||||
self._det_records[self._det_count * 2 + 1] = uint64(size)
|
||||
self._det_count += 1
|
||||
# Records exist for the detection probes only ([DET] overlap / is_alive / 0xCD
|
||||
# use-after-free fill) and for the record-mirror line of the OOM report. Keeping them
|
||||
# in production costs 16 B per alloc AND makes free's record-marking loop O(_det_count)
|
||||
# -- a linear scan of every allocation since the last reset, i.e. unbounded for a pool
|
||||
# that never resets (PermPool). So record only when detection is on.
|
||||
if self.detection != 0:
|
||||
if self._det_count >= self._det_cap:
|
||||
# Record table full: double it so is_alive/_det_probe never miss live blocks.
|
||||
new_cap: sizet = self._det_cap * 2
|
||||
new_rec: rawptr = rawptr(stdlib.realloc(self._det_arena, new_cap * 16))
|
||||
if new_rec is not None:
|
||||
self._det_arena = new_rec
|
||||
self._det_records = (uint64*)(new_rec)
|
||||
self._det_cap = new_cap
|
||||
if self._det_count < self._det_cap:
|
||||
self._det_records[self._det_count * 2] = rec_ptr_d
|
||||
self._det_records[self._det_count * 2 + 1] = uint64(size)
|
||||
self._det_count += 1
|
||||
# Probe decision: never entered when detection is off; with it on, deep_detect
|
||||
# probes every alloc, otherwise the historical sampling is used.
|
||||
if self.detection != 0 and ((self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0 or self.deep_detect != 0):
|
||||
@@ -1722,9 +1728,11 @@ class MemBuddy(MemManager):
|
||||
if self.detection != 0:
|
||||
# [DET] Returned ptr is 2 bytes past the user area; step back on free.
|
||||
p_eff = ptr - 2
|
||||
# Mark the record free (independent of detection): otherwise a later alloc of the
|
||||
# same address (normal reuse) would be misreported as an overlap.
|
||||
if self._det_records is not None:
|
||||
# Mark the record free so a later alloc of the same address (normal reuse) is not
|
||||
# misreported as an overlap. The record table is filled only in detection mode (see
|
||||
# alloc), so this O(_det_count) scan runs only there: a pool that never resets
|
||||
# (PermPool) otherwise paid a scan of every allocation ever made on each free.
|
||||
if self.detection != 0 and self._det_records is not None:
|
||||
# Read this record's real size (detection fills 0xCD after free, so a later
|
||||
# write-through breaks the fill = use-after-free). Keep the record and set the
|
||||
# free bit; the probe checks the 0xCD fill of still-free blocks.
|
||||
|
||||
Reference in new issue
Block a user