From 6473537f1e4d105c14fb429604e6910629bde5b6 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Mon, 11 May 2026 12:02:03 +0800 Subject: [PATCH] Skip gap-finder prefix via binary search find_free_gap_inner walked guest_t.regions[0..nregions) linearly and skipped any region with end <= gap_start using a cheap continue. With the cached post-allocation gap hint usually pointing high in the array, every mmap call still re-scanned the same skippable prefix on the way to the first interesting region, and the addr-hint direct caller atsys_mmap bypasses the hint cache entirely so it always paid the full prefix cost. Hoist the prefix walk into a static lower_bound helper first_region_end_above and start the existing loop at its result. Correctness rests on invariants already maintained elsewhere: guest_region_add keeps regions[] sorted ascending by start, and sys_mmap MAP_FIXED removes overlapping ranges before insertion (with adjacent anonymous regions coalesced via regions_mergeable), so ends are monotonically non-decreasing and binary-searchable. The defensive end <= gap_start continue stays in the body because ALIGN_UP(regions[i].end, hps) past one region can still skip over a smaller adjacent region whose end is below the new gap_start. No new data structures, no memory growth, no behavior change for callers; helps both the cached-hint path and the addr-hint direct call site. --- src/syscall/mem.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/syscall/mem.c b/src/syscall/mem.c index 289a9d6..7f7c374 100644 --- a/src/syscall/mem.c +++ b/src/syscall/mem.c @@ -228,6 +228,26 @@ static void split_regions_at_boundary(guest_t *g, uint64_t boundary) } } +/* Find the smallest i such that g->regions[i].end > gap_start. All earlier + * regions are entirely below gap_start and would be skipped by the loop body + * with no other effect. Regions are kept sorted by start and non-overlapping + * (sys_mmap MAP_FIXED removes overlaps before insertion), so ends are + * monotonic across the array and binary-searchable. + */ +static int first_region_end_above(const guest_t *g, uint64_t gap_start) +{ + int lo = 0; + int hi = g->nregions; + while (lo < hi) { + int mid = lo + (hi - lo) / 2; + if (g->regions[mid].end <= gap_start) + lo = mid + 1; + else + hi = mid; + } + return lo; +} + static uint64_t find_free_gap_inner(const guest_t *g, uint64_t length, uint64_t min_addr, @@ -243,8 +263,15 @@ static uint64_t find_free_gap_inner(const guest_t *g, size_t hps = host_page_size_cached(); uint64_t gap_start = ALIGN_UP(min_addr, hps); - for (int i = 0; i < g->nregions; i++) { - /* Skip regions entirely before the current search position */ + /* Skip the prefix of regions entirely below gap_start in O(log n). After a + * successful allocation the gap hint advances near or past the existing + * region tail, so the linear walk would otherwise re-scan that whole + * prefix on every mmap, addr-hint probe, or hint-miss full scan. + */ + for (int i = first_region_end_above(g, gap_start); i < g->nregions; i++) { + /* A region can still slip below gap_start after the ALIGN_UP advance + * below skips past a smaller adjacent region; keep the cheap guard. + */ if (g->regions[i].end <= gap_start) continue;