Skip to content

Commit dae0ae8

Browse files
committed
hg_malloc: report max_used as the peak of real_used, not the peak carve
max_used was the high-water mark of hb->real_used, which in the slab path is the CARVE total. Carve only ever grows - each worker carves its own chunk the first time it touches a size class - so max_used climbed indefinitely and drifted away from real_used instead of meaning what it means for every other allocator: the peak that real_used reached. Track the peak of the reported live figure separately and report that. The mark is sampled when the stats are read, so a spike between two reads can be missed, and the unsynchronized update is a benign race - a lost update can only under-report. The peak carve is still kept in max_real_used; exposing it belongs in a stat of its own, not in max_used.
1 parent 522c01c commit dae0ae8

2 files changed

Lines changed: 24 additions & 7 deletions

File tree

‎mem/hg_malloc.c‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -463,11 +463,12 @@ void hg_info(struct hg_block *hb, struct mem_info *info)
463463
info->real_used = hb->real_used > recycled ? hb->real_used - recycled : 0;
464464
info->free = hb->size - info->real_used;
465465

466-
/* peak carve: chunks are only ever carved when demand outruns the
467-
* recycled supply, so this is the high-water mark of live commitment
468-
* (and a safe upper bound on it), and stays monotonic like every other
469-
* allocator's max_used */
470-
info->max_used = hb->max_real_used;
466+
/* the peak of what real_used above actually reached - NOT the peak
467+
* carve (hb->max_real_used), which only ever grows and would drift
468+
* away from real_used forever */
469+
if (info->real_used > hb->max_live_used)
470+
hb->max_live_used = info->real_used;
471+
info->max_used = hb->max_live_used;
471472
info->total_frags = hg_fragments(hb);
472473
}
473474

‎mem/hg_malloc.h‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ struct hg_block {
229229
* lock-free fast path, so a plain field is safe here. */
230230
unsigned long real_used;
231231
unsigned long max_real_used;
232+
/* High-water mark of the LIVE figure that hg_get_real_used() reports, as
233+
* opposed to max_real_used above, which is the peak CARVE. They are
234+
* different quantities: carve only ever grows (chunks are never
235+
* un-carved), so reporting it as max_used made max_used drift away from
236+
* real_used forever instead of meaning "the peak real_used reached" -
237+
* unlike every other allocator. Sampled, not exact: refreshed whenever
238+
* the stats are read, so a spike between two reads can be missed. The
239+
* unsynchronized max update is a benign race - a lost update can only
240+
* under-report, never over-report. */
241+
unsigned long max_live_used;
232242

233243
/* the fast-path counters - see struct hg_pstat above. Summed by
234244
* hg_used()/hg_fragments(); never read directly. */
@@ -435,12 +445,18 @@ static inline unsigned long hg_get_free(struct hg_block *hb)
435445
static inline unsigned long hg_get_real_used(struct hg_block *hb)
436446
{
437447
unsigned long recycled = hg_slab_recycled(hb);
448+
unsigned long live = hb->real_used > recycled ?
449+
hb->real_used - recycled : 0;
438450

439-
return hb->real_used > recycled ? hb->real_used - recycled : 0;
451+
if (live > hb->max_live_used)
452+
hb->max_live_used = live;
453+
return live;
440454
}
441455
static inline unsigned long hg_get_max_real_used(struct hg_block *hb)
442456
{
443-
return hb->max_real_used;
457+
/* refresh the mark first, so reading max on its own is not stale */
458+
(void)hg_get_real_used(hb);
459+
return hb->max_live_used;
444460
}
445461
static inline unsigned long hg_get_frags(struct hg_block *hb)
446462
{

0 commit comments

Comments
 (0)