From 963dfc5a3c91d7085f5455bc1b44d83f4e05320a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 24 Oct 2026 13:31:51 +0800 Subject: [PATCH] =?UTF-8?q?perf(memhub):=20=E5=88=86=E9=85=8D=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E8=A1=A8=E6=94=B9=E4=B8=BA=E4=BB=85=20--detection=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=EF=BC=8Cfree=20=E4=B8=8D=E5=86=8D=E7=BA=BF?= =?UTF-8?q?=E6=80=A7=E6=89=AB=E6=8F=8F=E5=85=A8=E9=83=A8=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 记录区此前与 detection 解耦、每次 alloc 都写一条,_det_count 对永不 reset 的 PermPool 无上限增长;free 归还时按 _det_count 线性查找记录并置空闲位,于是每次 free 都是 O(历史分配数)。改为 detection==0 时不分配记录区也不记录/不回填,free 变 O(1);OOM 报告的块序分布走 chk_stack 不受影响。 --- memhub.vp | 58 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/memhub.vp b/memhub.vp index ae37472..2a77fc2 100644 --- a/memhub.vp +++ b/memhub.vp @@ -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.