Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/2225592/?format=api
{ "id": 2225592, "url": "http://patchwork.ozlabs.org/api/1.2/patches/2225592/?format=api", "web_url": "http://patchwork.ozlabs.org/project/glibc/patch/b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com/", "project": { "id": 41, "url": "http://patchwork.ozlabs.org/api/1.2/projects/41/?format=api", "name": "GNU C Library", "link_name": "glibc", "list_id": "libc-alpha.sourceware.org", "list_email": "libc-alpha@sourceware.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com>", "list_archive_url": null, "date": "2026-04-21T08:39:12", "name": "[RFC,1/1] malloc: madvise interior free chunks above a threshold", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "02c4e6160e99c2e2864e44812331037afcbcc439", "submitter": { "id": 93202, "url": "http://patchwork.ozlabs.org/api/1.2/people/93202/?format=api", "name": "Xavier Roche", "email": "xavier.roche@algolia.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/glibc/patch/b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com/mbox/", "series": [ { "id": 500760, "url": "http://patchwork.ozlabs.org/api/1.2/series/500760/?format=api", "web_url": "http://patchwork.ozlabs.org/project/glibc/list/?series=500760", "date": "2026-04-21T08:39:12", "name": "malloc: madvise interior free chunks above a threshold", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/500760/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2225592/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2225592/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "libc-alpha@sourceware.org" ], "Delivered-To": [ "patchwork-incoming@legolas.ozlabs.org", "libc-alpha@sourceware.org" ], "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=algolia.com header.i=@algolia.com header.a=rsa-sha256\n header.s=google header.b=K1XRfuO0;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)", "sourceware.org;\n\tdkim=pass (1024-bit key,\n unprotected) header.d=algolia.com header.i=@algolia.com header.a=rsa-sha256\n header.s=google header.b=K1XRfuO0", "sourceware.org;\n dmarc=pass (p=reject dis=none) header.from=algolia.com", "sourceware.org; spf=pass smtp.mailfrom=algolia.com", "server2.sourceware.org;\n arc=none smtp.remote-ip=2a00:1450:4864:20::330" ], "Received": [ "from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g0G4f3m7bz1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 18:39:46 +1000 (AEST)", "from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id AEE3D4BA9020\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 08:39:44 +0000 (GMT)", "from mail-wm1-x330.google.com (mail-wm1-x330.google.com\n [IPv6:2a00:1450:4864:20::330])\n by sourceware.org (Postfix) with ESMTPS id 4F9204BA901A\n for <libc-alpha@sourceware.org>; Tue, 21 Apr 2026 08:39:16 +0000 (GMT)", "by mail-wm1-x330.google.com with SMTP id\n 5b1f17b1804b1-4891b02a0acso2588965e9.3\n for <libc-alpha@sourceware.org>; Tue, 21 Apr 2026 01:39:16 -0700 (PDT)", "from xavier-thinkpad.. ([2a01:e0a:1048:49a0:2de4:14b:d3be:34fd])\n by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-488fc17f642sm322503005e9.5.2026.04.21.01.39.13\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 21 Apr 2026 01:39:14 -0700 (PDT)" ], "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 sourceware.org AEE3D4BA9020", "OpenDKIM Filter v2.11.0 sourceware.org 4F9204BA901A" ], "DMARC-Filter": "OpenDMARC Filter v1.4.2 sourceware.org 4F9204BA901A", "ARC-Filter": "OpenARC Filter v1.0.0 sourceware.org 4F9204BA901A", "ARC-Seal": "i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776760756; cv=none;\n b=Ey+4sdgpho7ZrCvwesMhWZVsu8hhZi15e4BwZIQL6HnTihPEUAts090ICI58cRxE+xq1jO/dk1KmWObmqDfbCq6NwYdjHWApVr4oM6Z38T+/dPXaewqvsRUKKs5fHACgI9d3HHchiop3dexO9qtmTWxmlJwVzGcw2G1ItlkSwek=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776760756; c=relaxed/simple;\n bh=3k4W6ey+nA0vELAvxA3MFaYO98jMPKhbdw6ODPBAf3U=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=UsX6t0p+PLLuzG/+uC3hTIH3nFBo1OyMQlgX/JF+WhJsnHpix49ZuPOv20Oz2Clzm9k2aDGlYDSlBXHs443uGOPws7Ao1ZB6+oo5ebjlzRfgmq9KXG3C36pAYU124mqiG77tnsjPsh1I7CdLIly6htjZdQ+bi06eptWJALRp+Ck=", "ARC-Authentication-Results": "i=1; server2.sourceware.org", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=algolia.com; s=google; t=1776760755; x=1777365555; darn=sourceware.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=KBzkjlCZ6F7UM3zxSSnv4swwVMAHvO4G1orT1kQ9vII=;\n b=K1XRfuO0NK/jp6ZZX0XK/CZdypfl2hUED10ZxkxNSlAZKrpHjUOyUwplePS4ZYeBnB\n wn52mB9vCpsZ09mxwaWju01qbzssAF9hMM8VS7WdxxCtm1S/7gPBJxKnZ0pojBcJ0Jms\n 1N8Cqg1Jox4vyL2EmM+MPJYjyd8nAeJBaA66Q=", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776760755; x=1777365555;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=KBzkjlCZ6F7UM3zxSSnv4swwVMAHvO4G1orT1kQ9vII=;\n b=KgxH2yqenwufeSTSc/TS6Ibal8wMFRU5TFczULYtFRcJPeIgu3zGqc2UJq0LzKhWpe\n PAdqYfjheqZfiX8n78wDQbFUvRtCj02N3xSKtsEhaIJc/Lb0UHS67/G/d0Rz3mrDPdmF\n US+TS/oG8a7aJHa/iIIKqIFODQRbZZuGexOgkGSd8sfdbvJxnyj9HFxs4+h6AgVC+nLK\n QXq0HYDvF1rhSNMuBNqGpf2haRx2LOdgF2baUupQoXcniIlM6VxSBnlaR0Z7flTSokEH\n ff9tM59rXfgyWQk2H38f3U/gsQ9ry5iYKh9is5Mq7YzvXQC9O7xhKJYK99TtqUs/P0eQ\n btxA==", "X-Gm-Message-State": "AOJu0YzRvxqK9xwDlp5rDlxLhdz0mRoIlKhwFWwqpOYHpEa0KfH7kKXn\n gug8cid6BYfsR7pp0OmfG3k8Y0qNWOo60fjXhxA65HbVcft9++7nLx4iIO0A8z4j8AOamdAeFgw\n 7iz5/", "X-Gm-Gg": "AeBDietq5xudFOkXpv4ECb4ZyoI5R2O3bNbOmteZ30VGqiyf1g75aqKmRzgps3w2Apb\n Ana3MkZEBOpiC45LdQessMLO0fVYZ4dejrpa+bfTXt7VDaKLzDb8Wpjss3FCE4JJHLZmagDvNXS\n 8mIhobkpr3mQB2kDm1W6aT/UQwcg4PjzEBUqy9DsYBPkH8nYJt46nBbAFrrGbY07oWzJ8WOp5J+\n RCro1dMxkAOn+ObEHJvM1lL7///T8bzyhUssQkS8/Rvb9g9tbM622sO7kylcyCtia2J3ArL/Upa\n J2tPhUp7bqhLeWnTjdnz9lftjlu8Fr2mDhOGBSeHzyC2ojmyZI/sjMOqjgo9IDRgVTo3mWZj9QQ\n /jHqMKyn2aPHc4+xwiclwy8nvecJ768Pwoy8qzuziBhQOXKGCn3ewtmnAK2tLmQO8yxv9bhTmd1\n Zp9qgWqXPNlnHdCpna7ULUhUdY6qdtBWP6eONaxIDmAl/KiWVpkw==", "X-Received": "by 2002:a05:600c:4247:b0:489:1dc6:d6e with SMTP id\n 5b1f17b1804b1-4891dc60e71mr27937035e9.1.1776760754649;\n Tue, 21 Apr 2026 01:39:14 -0700 (PDT)", "From": "Xavier Roche <xavier.roche@algolia.com>", "To": "libc-alpha@sourceware.org", "Cc": "wilco.dijkstra@arm.com,\n\tadhemerval.zanella@linaro.org", "Subject": "[RFC PATCH 1/1] malloc: madvise interior free chunks above a\n threshold", "Date": "Tue, 21 Apr 2026 10:39:12 +0200", "Message-ID": "\n <b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com>", "X-Mailer": "git-send-email 2.43.0", "In-Reply-To": "<cover.1776760573.git.xavier.roche@algolia.com>", "References": "<cover.1776760573.git.xavier.roche@algolia.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "libc-alpha@sourceware.org", "X-Mailman-Version": "2.1.30", "Precedence": "list", "List-Id": "Libc-alpha mailing list <libc-alpha.sourceware.org>", "List-Unsubscribe": "<https://sourceware.org/mailman/options/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=unsubscribe>", "List-Archive": "<https://sourceware.org/pipermail/libc-alpha/>", "List-Post": "<mailto:libc-alpha@sourceware.org>", "List-Help": "<mailto:libc-alpha-request@sourceware.org?subject=help>", "List-Subscribe": "<https://sourceware.org/mailman/listinfo/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=subscribe>", "Errors-To": "libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org" }, "content": "Since glibc 2.26 introduced tcache, free() no longer returns\nphysical pages to the kernel for chunks that sit in the interior\nof an arena heap. malloc_trim(0) still recovers the memory, but\nfree() itself cannot. A bisect between 2.25 and 2.26 shows a 4x\nRSS regression on a reproducer that interleaves long-lived index\ndata with short-lived filler allocations.\n\nExtend _int_free_maybe_trim to madvise the page-aligned interior\nof consolidated chunks >= ATTEMPT_TRIMMING_THRESHOLD (64 KB),\nusing the same page-alignment logic as mtrim. To avoid a flood\nof madvise calls when many small frees merge into one chunk (the\nconcern raised by Wilco on BZ #33886 comment 10), gate the call\non the caller's pre-consolidation size: direct madvise when that\nsize already covers a full page, accumulator-batched madvise\n(fires every MADVISE_PURGE_THRESHOLD = 256 KB of sub-page frees)\notherwise.\n\nThe per-arena accumulator is read and written only under the\narena mutex, which is held by both call sites\n(_int_free_merge_chunk and _int_memalign).\n\nAdvice type is MADV_FREE for moderate chunks and MADV_DONTNEED\nfor chunks >= 2 * ATTEMPT_TRIMMING_THRESHOLD. MADV_DONTNEED\nmatches the existing mtrim behavior and gives operators the\npredictable RSS drop they expect; MADV_FREE amortises the\nper-page cost for moderate chunks that are likely to be reused.\n\nReproducer (tst-madvise-threshold): 16 threads, 256 MB live\ndata, 10 GB short-lived churn.\n\n RSS after free, before: 1247 MB\n RSS after free, after: 296 MB\n Runtime overhead: +0.16 s on a tight malloc/free loop.\n\nRelated: BZ #15321, #18910, #27976, #33886.\n\nSigned-off-by: Xavier Roche <xavier.roche@algolia.com>\n---\n malloc/Makefile | 1 +\n malloc/malloc.c | 61 +++++++++++++---\n malloc/tst-madvise-threshold.c | 128 +++++++++++++++++++++++++++++++++\n 3 files changed, 182 insertions(+), 8 deletions(-)\n create mode 100644 malloc/tst-madvise-threshold.c", "diff": "diff --git a/malloc/Makefile b/malloc/Makefile\nindex fef5021298..d663454e57 100644\n--- a/malloc/Makefile\n+++ b/malloc/Makefile\n@@ -39,6 +39,7 @@ tests := \\\n tst-free-sized-trace \\\n tst-interpose-nothread \\\n tst-interpose-thread \\\n+ tst-madvise-threshold \\\n tst-mallinfo2 \\\n tst-malloc \\\n tst-malloc-alternate-path \\\ndiff --git a/malloc/malloc.c b/malloc/malloc.c\nindex 57b58382b1..d20e22a463 100644\n--- a/malloc/malloc.c\n+++ b/malloc/malloc.c\n@@ -1029,7 +1029,8 @@ static void _int_free_merge_chunk (mstate, mchunkptr, INTERNAL_SIZE_T);\n static INTERNAL_SIZE_T _int_free_create_chunk (mstate,\n \t\t\t\t\t mchunkptr, INTERNAL_SIZE_T,\n \t\t\t\t\t mchunkptr, INTERNAL_SIZE_T);\n-static void _int_free_maybe_trim (mstate, INTERNAL_SIZE_T);\n+static void _int_free_maybe_trim (mstate, mchunkptr, INTERNAL_SIZE_T,\n+\t\t\t\t INTERNAL_SIZE_T);\n static void* _int_realloc(mstate, mchunkptr, INTERNAL_SIZE_T,\n \t\t\t INTERNAL_SIZE_T);\n static void* _int_memalign(mstate, size_t, size_t);\n@@ -1691,6 +1692,18 @@ unlink_chunk (mstate av, mchunkptr p)\n \n #define ATTEMPT_TRIMMING_THRESHOLD (65536UL)\n \n+/* Cumulative bytes freed per arena before triggering madvise for\n+ sub-page frees that individually skip the page-size gate. */\n+\n+#define MADVISE_PURGE_THRESHOLD (4 * ATTEMPT_TRIMMING_THRESHOLD)\n+\n+/* Consolidated chunks above this size use MADV_DONTNEED (immediate\n+ page release) instead of MADV_FREE (lazy release). Large chunks\n+ are unlikely to be reused at the same size, and the immediate RSS\n+ reduction is worth the higher per-call cost. */\n+\n+#define MADVISE_DONTNEED_THRESHOLD (2 * ATTEMPT_TRIMMING_THRESHOLD)\n+\n /*\n NONCONTIGUOUS_BIT indicates that MORECORE does not return contiguous\n regions. Otherwise, contiguity is exploited in merging together,\n@@ -1747,6 +1760,9 @@ struct malloc_state\n /* Memory allocated from the system in this arena. */\n INTERNAL_SIZE_T system_mem;\n INTERNAL_SIZE_T max_system_mem;\n+\n+ /* Cumulative sub-page bytes freed since the last madvise. */\n+ INTERNAL_SIZE_T madvise_accumulator;\n };\n \n struct malloc_par\n@@ -4315,6 +4331,7 @@ _int_free_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size, int have_lock)\n static void\n _int_free_merge_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size)\n {\n+ INTERNAL_SIZE_T orig_size = size;\n mchunkptr nextchunk = chunk_at_offset(p, size);\n \n check_inuse_chunk (av, p);\n@@ -4352,7 +4369,7 @@ _int_free_merge_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size)\n \n /* Write the chunk header, maybe after merging with the following chunk. */\n size = _int_free_create_chunk (av, p, size, nextchunk, nextsize);\n- _int_free_maybe_trim (av, size);\n+ _int_free_maybe_trim (av, p, orig_size, size);\n }\n \n /* Create a chunk at P of SIZE bytes, with SIZE potentially increased\n@@ -4432,14 +4449,41 @@ _int_free_create_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size,\n }\n \n /* If the total unused topmost memory exceeds trim threshold, ask malloc_trim\n- to reduce top. */\n+ to reduce top. Also release physical pages from interior free chunks. */\n static void\n-_int_free_maybe_trim (mstate av, INTERNAL_SIZE_T size)\n+_int_free_maybe_trim (mstate av, mchunkptr p,\n+\t\t INTERNAL_SIZE_T orig_size, INTERNAL_SIZE_T size)\n {\n- /* We don't want to trim on each free. As a compromise, trimming is attempted\n- if ATTEMPT_TRIMMING_THRESHOLD is reached. */\n if (size >= ATTEMPT_TRIMMING_THRESHOLD)\n {\n+ /* Release interior pages of the consolidated chunk. MADV_FREE\n+\t for moderate chunks (pages kept until kernel pressure, no\n+\t re-fault on quick reuse). MADV_DONTNEED for large chunks\n+\t (immediate RSS reduction, worth the cost at this size).\n+\t Sub-page frees accumulate until MADVISE_PURGE_THRESHOLD. */\n+ size_t ps = GLRO (dl_pagesize);\n+ bool do_madvise\n+\t= (orig_size >= ps + sizeof (struct malloc_chunk));\n+ if (!do_madvise)\n+\t{\n+\t av->madvise_accumulator += orig_size;\n+\t if (av->madvise_accumulator >= MADVISE_PURGE_THRESHOLD)\n+\t do_madvise = true;\n+\t}\n+ if (do_madvise)\n+\t{\n+\t char *paligned = PTR_ALIGN_UP ((char *) p\n+\t\t\t\t\t + sizeof (struct malloc_chunk), ps);\n+\t char *pend = PTR_ALIGN_DOWN ((char *) p + size, ps);\n+\t if (pend > paligned)\n+\t {\n+\t int advice = (size >= MADVISE_DONTNEED_THRESHOLD)\n+\t\t\t ? MADV_DONTNEED : MADV_FREE;\n+\t __madvise (paligned, pend - paligned, advice);\n+\t av->madvise_accumulator = 0;\n+\t }\n+\t}\n+\n if (av == &main_arena)\n \t{\n #ifndef MORECORE_CANNOT_TRIM\n@@ -4646,9 +4690,10 @@ _int_memalign (mstate av, size_t alignment, size_t bytes)\n mchunkptr nextchunk = chunk_at_offset (p, size);\n mchunkptr remainder = chunk_at_offset (p, nb);\n set_head_size (p, nb);\n- size = _int_free_create_chunk (av, remainder, size - nb, nextchunk,\n+ INTERNAL_SIZE_T remainder_size = size - nb;\n+ size = _int_free_create_chunk (av, remainder, remainder_size, nextchunk,\n \t\t\t\t chunksize (nextchunk));\n- _int_free_maybe_trim (av, size);\n+ _int_free_maybe_trim (av, remainder, remainder_size, size);\n }\n \n check_inuse_chunk (av, p);\ndiff --git a/malloc/tst-madvise-threshold.c b/malloc/tst-madvise-threshold.c\nnew file mode 100644\nindex 0000000000..964ec0ba30\n--- /dev/null\n+++ b/malloc/tst-madvise-threshold.c\n@@ -0,0 +1,128 @@\n+/* Test for the glibc.malloc.madvise_threshold tunable.\n+\n+ Verify that when the tunable is set, free() returns physical memory\n+ to the OS for interior free chunks (not just the top chunk).\n+\n+ Copyright (C) 2026 Free Software Foundation, Inc.\n+ This file is part of the GNU C Library.\n+\n+ The GNU C Library is free software; you can redistribute it and/or\n+ modify it under the terms of the GNU Lesser General Public\n+ License as published by the Free Software Foundation; either\n+ version 2.1 of the License, or (at your option) any later version.\n+\n+ The GNU C Library is distributed in the hope that it will be useful,\n+ but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n+ Lesser General Public License for more details.\n+\n+ You should have received a copy of the GNU Lesser General Public\n+ License along with the GNU C Library; if not, see\n+ <https://www.gnu.org/licenses/>. */\n+\n+#include <malloc.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <support/check.h>\n+\n+/* Read RSS from /proc/self/statm in bytes. */\n+static long\n+get_rss (void)\n+{\n+ FILE *f = fopen (\"/proc/self/statm\", \"r\");\n+ if (f == NULL)\n+ FAIL_UNSUPPORTED (\"/proc/self/statm not available\");\n+\n+ long pages;\n+ if (fscanf (f, \"%*d %ld\", &pages) != 1)\n+ {\n+ fclose (f);\n+ FAIL_UNSUPPORTED (\"cannot parse /proc/self/statm\");\n+ }\n+ fclose (f);\n+ return pages * sysconf (_SC_PAGESIZE);\n+}\n+\n+/* Number of pinning (index) allocations. These stay alive and\n+ prevent heap segments from being unmapped. */\n+#define N_INDEX 200\n+\n+/* Number of filler (query) allocations per round. These are freed\n+ and should have their physical pages returned when the tunable\n+ is set. */\n+#define N_FILLER 2000\n+\n+/* Size of each filler allocation. Must be below mmap threshold\n+ so allocations go through arenas. */\n+#define FILLER_SIZE (64 * 1024)\n+\n+/* Size of each index allocation. Small enough to fit between\n+ filler chunks. */\n+#define INDEX_SIZE 1024\n+\n+static int\n+do_test (void)\n+{\n+ void *index_ptrs[N_INDEX];\n+ void *filler_ptrs[N_FILLER];\n+\n+ /* Phase 1: Allocate index and filler data interleaved.\n+ This creates the fragmentation pattern: index chunks scattered\n+ among filler chunks in the arena heaps. */\n+ int idx = 0;\n+ for (int i = 0; i < N_FILLER; i++)\n+ {\n+ /* Every N_FILLER/N_INDEX filler allocs, insert an index alloc. */\n+ if (idx < N_INDEX && i % (N_FILLER / N_INDEX) == 0)\n+\t{\n+\t index_ptrs[idx] = malloc (INDEX_SIZE);\n+\t TEST_VERIFY_EXIT (index_ptrs[idx] != NULL);\n+\t memset (index_ptrs[idx], 0xAA, INDEX_SIZE);\n+\t idx++;\n+\t}\n+\n+ filler_ptrs[i] = malloc (FILLER_SIZE);\n+ TEST_VERIFY_EXIT (filler_ptrs[i] != NULL);\n+ memset (filler_ptrs[i], 0xBB, FILLER_SIZE);\n+ }\n+\n+ long rss_peak = get_rss ();\n+ printf (\"RSS after allocation: %ld MB\\n\", rss_peak / (1024 * 1024));\n+\n+ /* Phase 2: Free all filler data. Index data stays alive and\n+ pins the heap segments, so the freed space is interior. */\n+ for (int i = 0; i < N_FILLER; i++)\n+ free (filler_ptrs[i]);\n+\n+ long rss_after_free = get_rss ();\n+ printf (\"RSS after free: %ld MB\\n\", rss_after_free / (1024 * 1024));\n+\n+ /* Phase 3: Check that RSS dropped.\n+ With madvise_threshold set (via GLIBC_TUNABLES in the test\n+ environment), free() calls madvise(MADV_DONTNEED) on the\n+ interior of the freed chunks, so RSS should drop.\n+\n+ Without the tunable, RSS stays near the peak because the freed\n+ memory is interior to the heap, not at the top.\n+\n+ We expect at least 50% of the filler memory to be returned. */\n+ long filler_bytes = (long) N_FILLER * FILLER_SIZE;\n+ long recovered = rss_peak - rss_after_free;\n+\n+ printf (\"Filler data: %ld MB\\n\", filler_bytes / (1024 * 1024));\n+ printf (\"Recovered by free(): %ld MB\\n\", recovered / (1024 * 1024));\n+\n+ /* The threshold is set via the test environment. If it's working,\n+ we should recover at least half the filler memory. */\n+ TEST_VERIFY (recovered > filler_bytes / 2);\n+\n+ /* Cleanup. */\n+ for (int i = 0; i < N_INDEX; i++)\n+ free (index_ptrs[i]);\n+\n+ return 0;\n+}\n+\n+#include <support/test-driver.c>\n", "prefixes": [ "RFC", "1/1" ] }