Paul Eggert wrote:
> > 1) change the default back from posix_memalign to mmap,
> > except on OpenBSD.
> > 2) since the "list" is not going away, use a different implementation
> > for it: either prev/next fields (doubly-linked list) or a hash-set
> > container.
>
> Although I wouldn't bother doing either (1) or (2) I don't object to
> doing (1) if (2) is also done, given that CVS is the only user.
Done through the attached patches.
0001 fixes an embarrassing mistake, and includes a correction of the
invocation of VirtualFree. With these fixes, the results table looks
very reasonable:
b c d e f
glibc 176% 101% 175% --- ---
musl libc 198% 101% 197% --- ---
macOS 190% 100% 195% --- ---
FreeBSD 190% 100% 380% --- ---
NetBSD 185% 101% 379% --- ---
OpenBSD 177% 101% 101% --- ---
AIX 177% 101% 176% --- ---
Solaris 11.4 176% 101% 175% --- ---
Cygwin 181% 100% 181% --- ---
native Windows 184% --- --- 180% 100%
Android 182% 101% 181% --- ---
0002 replaces the hand-written ad-hoc linked list with a use of the hash-map
container, which 'void *' as key type and 'void *' as value type. This is
a real simplification: The size of the file shrinks by more than 30 lines.
0003 adds the scalability unit test.
0004 changes the default implementation by considering the table above:
mmap or VirtualAlloc, except on OpenBSD. Thus eliminating the big waste
of memory that the use of posix_memalign introduced a couple of days ago.
Bruno
>From 8bca9f3cc8706785632b093e3e6f00b7df7b35f8 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sat, 13 Sep 2025 22:36:06 +0200
Subject: [PATCH 1/4] pagealign_alloc: Fix crashes (regression 2025-09-11).
* lib/pagealign_alloc.c (pagealign_alloc, pagealign_free): Add missing
'break' statements. For PA_IMPL_VIRTUAL_ALLOC, don't use new_memnode and
get_memnode.
---
ChangeLog | 7 +++++++
lib/pagealign_alloc.c | 12 +++++++-----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index f53d4204fe..d585d51c49 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2025-09-13 Bruno Haible <[email protected]>
+
+ pagealign_alloc: Fix crashes (regression 2025-09-11).
+ * lib/pagealign_alloc.c (pagealign_alloc, pagealign_free): Add missing
+ 'break' statements. For PA_IMPL_VIRTUAL_ALLOC, don't use new_memnode and
+ get_memnode.
+
2025-09-13 Paul Eggert <[email protected]>
endian: port to Solaris 11.4 and macOS 15
diff --git a/lib/pagealign_alloc.c b/lib/pagealign_alloc.c
index 28aca9baf5..0e2ccdf7e0 100644
--- a/lib/pagealign_alloc.c
+++ b/lib/pagealign_alloc.c
@@ -61,7 +61,7 @@ typedef union
For each memory region, we store the original pointer returned by
malloc(). */
void *pointer;
- /* For PA_IMPL_MMAP, PA_IMPL_VIRTUAL_ALLOC:
+ /* For PA_IMPL_MMAP:
For each memory region, we store its size. */
size_t size;
} info_t;
@@ -215,9 +215,7 @@ pagealign_alloc (size_t size)
errno = ENOMEM;
return NULL;
}
- info_t info;
- info.size = size;
- new_memnode (ret, info);
+ break;
#else
errno = ENOSYS;
return NULL;
@@ -261,6 +259,7 @@ pagealign_free (void *aligned_ptr)
#if HAVE_SYS_MMAN_H
if (munmap (aligned_ptr, get_memnode (aligned_ptr).size) < 0)
error (EXIT_FAILURE, errno, "Failed to unmap memory");
+ break;
#else
abort ();
#endif
@@ -268,6 +267,7 @@ pagealign_free (void *aligned_ptr)
case PA_IMPL_POSIX_MEMALIGN:
#if HAVE_POSIX_MEMALIGN
free (aligned_ptr);
+ break;
#else
abort ();
#endif
@@ -277,6 +277,7 @@ pagealign_free (void *aligned_ptr)
/* Documentation:
<https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free> */
_aligned_free (aligned_ptr);
+ break;
#else
abort ();
#endif
@@ -285,8 +286,9 @@ pagealign_free (void *aligned_ptr)
#if defined _WIN32 && !defined __CYGWIN__
/* Documentation:
<https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree> */
- if (!VirtualFree (aligned_ptr, get_memnode (aligned_ptr).size, MEM_RELEASE))
+ if (!VirtualFree (aligned_ptr, 0, MEM_RELEASE))
error (EXIT_FAILURE, 0, "Failed to free memory");
+ break;
#else
abort ();
#endif
--
2.50.1
>From 9437102692cb2fc6f0f9a48753c5ab3f81511ef7 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 14 Sep 2025 00:24:21 +0200
Subject: [PATCH 2/4] pagealign_alloc: Fix scalability problem.
Reported by Harry Sintonen <[email protected]> in
<https://lists.gnu.org/archive/html/bug-gnulib/2025-09/msg00108.html>.
* lib/pagealign_alloc.c: Include gl_xmap.h, gl_hash_map.h. Don't include
xalloc.h.
(info_t, memnode_t, struct memnode_s): Remove types.
(memnode_table): Remove variable.
(new_memnode, get_memnode): Remove functions.
(page_info_map): New variable.
(pagealign_alloc): Use gl_map_put instead of new_memnode.
(pagealign_free): Use gl_map_getremove instead of get_memnode.
* modules/pagealign_alloc (Depends-on): Add xmap, hash-map. Remove
xalloc.
---
ChangeLog | 16 +++++++
lib/pagealign_alloc.c | 104 +++++++++++++---------------------------
modules/pagealign_alloc | 3 +-
3 files changed, 51 insertions(+), 72 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index d585d51c49..e0ab0ed821 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2025-09-13 Bruno Haible <[email protected]>
+
+ pagealign_alloc: Fix scalability problem.
+ Reported by Harry Sintonen <[email protected]> in
+ <https://lists.gnu.org/archive/html/bug-gnulib/2025-09/msg00108.html>.
+ * lib/pagealign_alloc.c: Include gl_xmap.h, gl_hash_map.h. Don't include
+ xalloc.h.
+ (info_t, memnode_t, struct memnode_s): Remove types.
+ (memnode_table): Remove variable.
+ (new_memnode, get_memnode): Remove functions.
+ (page_info_map): New variable.
+ (pagealign_alloc): Use gl_map_put instead of new_memnode.
+ (pagealign_free): Use gl_map_getremove instead of get_memnode.
+ * modules/pagealign_alloc (Depends-on): Add xmap, hash-map. Remove
+ xalloc.
+
2025-09-13 Bruno Haible <[email protected]>
pagealign_alloc: Fix crashes (regression 2025-09-11).
diff --git a/lib/pagealign_alloc.c b/lib/pagealign_alloc.c
index 0e2ccdf7e0..332306bd41 100644
--- a/lib/pagealign_alloc.c
+++ b/lib/pagealign_alloc.c
@@ -39,7 +39,8 @@
#endif
#include <error.h>
-#include "xalloc.h"
+#include "gl_xmap.h"
+#include "gl_hash_map.h"
#include "gettext.h"
#define _(msgid) dgettext (GNULIB_TEXT_DOMAIN, msgid)
@@ -55,68 +56,14 @@
/* Implementation of pagealign_alloc. */
pagealign_impl_t pagealign_impl;
-typedef union
-{
- /* For PA_IMPL_MALLOC:
- For each memory region, we store the original pointer returned by
- malloc(). */
- void *pointer;
- /* For PA_IMPL_MMAP:
+/* Map:
+ For PA_IMPL_MALLOC:
+ aligned_ptr -> void *.
+ For each memory region, we store the original pointer returned by malloc().
+ For PA_IMPL_MMAP:
+ aligned_ptr -> size_t.
For each memory region, we store its size. */
- size_t size;
-} info_t;
-
-
-/* For PA_IMPL_MALLOC, PA_IMPL_MMAP. */
-
-/* A simple linked list of allocated memory regions. It is probably not the
- most efficient way to store these, but anyway... */
-typedef struct memnode_s memnode_t;
-struct memnode_s
-{
- void *aligned_ptr;
- info_t info;
- memnode_t *next;
-};
-
-/* The list of currently allocated memory regions. */
-static memnode_t *memnode_table = NULL;
-
-static void
-new_memnode (void *aligned_ptr, info_t info)
-{
- memnode_t *new_node = XMALLOC (memnode_t);
- new_node->aligned_ptr = aligned_ptr;
- new_node->info = info;
- new_node->next = memnode_table;
- memnode_table = new_node;
-}
-
-/* Dispose of the memnode containing a map for the ALIGNED_PTR in question
- and return the content of the node's INFO field. */
-static info_t
-get_memnode (void *aligned_ptr)
-{
- info_t ret;
- memnode_t *c;
- memnode_t **p_next = &memnode_table;
-
- for (c = *p_next; c != NULL; p_next = &c->next, c = c->next)
- if (c->aligned_ptr == aligned_ptr)
- break;
-
- if (c == NULL)
- /* An attempt to free untracked memory. A wrong pointer was passed
- to pagealign_free(). */
- abort ();
-
- /* Remove this entry from the list, save the return value, and free it. */
- *p_next = c->next;
- ret = c->info;
- free (c);
-
- return ret;
-}
+static gl_map_t page_info_map;
/* Returns the default implementation. */
@@ -158,9 +105,10 @@ pagealign_alloc (size_t size)
}
ret = (char *) unaligned_ptr
+ ((- (uintptr_t) unaligned_ptr) & (pagesize - 1));
- info_t info;
- info.pointer = unaligned_ptr;
- new_memnode (ret, info);
+ if (page_info_map == NULL)
+ page_info_map =
+ gl_map_create_empty (GL_HASH_MAP, NULL, NULL, NULL, NULL);
+ gl_map_put (page_info_map, ret, unaligned_ptr);
}
break;
@@ -170,9 +118,10 @@ pagealign_alloc (size_t size)
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (ret == MAP_FAILED)
return NULL;
- info_t info;
- info.size = size;
- new_memnode (ret, info);
+ if (page_info_map == NULL)
+ page_info_map =
+ gl_map_create_empty (GL_HASH_MAP, NULL, NULL, NULL, NULL);
+ gl_map_put (page_info_map, ret, (void *) (uintptr_t) size);
break;
#else
errno = ENOSYS;
@@ -252,13 +201,26 @@ pagealign_free (void *aligned_ptr)
switch (impl)
{
case PA_IMPL_MALLOC:
- free (get_memnode (aligned_ptr).pointer);
+ {
+ const void *value;
+ if (page_info_map == NULL
+ || !gl_map_getremove (page_info_map, aligned_ptr, &value))
+ abort ();
+ void *unaligned_ptr = (void *) value;
+ free (unaligned_ptr);
+ }
break;
case PA_IMPL_MMAP:
#if HAVE_SYS_MMAN_H
- if (munmap (aligned_ptr, get_memnode (aligned_ptr).size) < 0)
- error (EXIT_FAILURE, errno, "Failed to unmap memory");
+ {
+ const void *value;
+ if (page_info_map == NULL
+ || !gl_map_getremove (page_info_map, aligned_ptr, &value))
+ abort ();
+ if (munmap (aligned_ptr, (size_t) (uintptr_t) value) < 0)
+ error (EXIT_FAILURE, errno, "Failed to unmap memory");
+ }
break;
#else
abort ();
diff --git a/modules/pagealign_alloc b/modules/pagealign_alloc
index 6570226665..22f8373c70 100644
--- a/modules/pagealign_alloc
+++ b/modules/pagealign_alloc
@@ -14,9 +14,10 @@ getpagesize
gettext-h
gnulib-i18n
stdlib-h
-xalloc
unistd-h
stdint-h
+xmap
+hash-map
configure.ac:
gl_PAGEALIGN_ALLOC
--
2.50.1
From 7f4d4edc36cd358836211021f6b4b6e3e9ce4d95 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 14 Sep 2025 00:25:12 +0200
Subject: [PATCH 3/4] pagealign_alloc: Add unit test for scalability.
* tests/test-pagealign_alloc.c: New file.
* modules/pagealign_alloc-tests (Files): Add it.
(Depends-on): Add xalloc.
(configure.ac): Check for alarm().
(Makefile.am): Arrange to compile and run test-pagealign_alloc.
---
ChangeLog | 7 +++
modules/pagealign_alloc-tests | 6 +++
tests/test-pagealign_alloc.c | 83 +++++++++++++++++++++++++++++++++++
3 files changed, 96 insertions(+)
create mode 100644 tests/test-pagealign_alloc.c
diff --git a/ChangeLog b/ChangeLog
index e0ab0ed821..78a8111497 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,12 @@
2025-09-13 Bruno Haible <[email protected]>
+ pagealign_alloc: Add unit test for scalability.
+ * tests/test-pagealign_alloc.c: New file.
+ * modules/pagealign_alloc-tests (Files): Add it.
+ (Depends-on): Add xalloc.
+ (configure.ac): Check for alarm().
+ (Makefile.am): Arrange to compile and run test-pagealign_alloc.
+
pagealign_alloc: Fix scalability problem.
Reported by Harry Sintonen <[email protected]> in
<https://lists.gnu.org/archive/html/bug-gnulib/2025-09/msg00108.html>.
diff --git a/modules/pagealign_alloc-tests b/modules/pagealign_alloc-tests
index bc9f16fc16..9e3bc64acc 100644
--- a/modules/pagealign_alloc-tests
+++ b/modules/pagealign_alloc-tests
@@ -1,13 +1,19 @@
Files:
+tests/test-pagealign_alloc.c
tests/bench-pagealign_alloc.c
Depends-on:
getpagesize
get-rusage-as
get-rusage-data
+xalloc
configure.ac:
+AC_CHECK_DECLS_ONCE([alarm])
Makefile.am:
+TESTS += test-pagealign_alloc
+check_PROGRAMS += test-pagealign_alloc
+
noinst_PROGRAMS += bench-pagealign_alloc
bench_pagealign_alloc_CPPFLAGS = $(AM_CPPFLAGS) -DNDEBUG
diff --git a/tests/test-pagealign_alloc.c b/tests/test-pagealign_alloc.c
new file mode 100644
index 0000000000..0b1374a589
--- /dev/null
+++ b/tests/test-pagealign_alloc.c
@@ -0,0 +1,83 @@
+/* Test pagealign_alloc module.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+/* Written by Bruno Haible <[email protected]>, 2025. */
+
+#include <config.h>
+
+/* Specification. */
+#include "pagealign_alloc.h"
+
+/* This test verifies that allocating and then freeing N pages is O(N),
+ not O(N??). */
+
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "xalloc.h"
+
+int
+main (int argc, char *argv[])
+{
+#if HAVE_DECL_ALARM
+ /* Declare failure if test takes too long, by using default abort
+ caused by SIGALRM.
+ On a fast x86_64 machine in 2025, with the PA_IMPL_MALLOC implementation,
+ the execution times are:
+ n | time | time |
+ | linear list | hash set |
+ -------+-------------+----------+
+ 10000 | 0.15 sec | 0.03 sec |
+ 25000 | 0.8 sec | 0.07 sec |
+ 50000 | 3.1 sec | 0.13 sec |
+ 100000 | 14 sec | 0.26 sec |
+ */
+ int alarm_value = 10;
+ signal (SIGALRM, SIG_DFL);
+ alarm (alarm_value);
+#endif
+
+ int n;
+ if (argc > 1)
+ n = atoi (argv[1]);
+ else
+ {
+#if defined __CYGWIN__
+ n = 25000;
+#else
+ n = (sizeof (void *) <= 4 ? 25000 : 50000);
+#endif
+ }
+
+ void **pages = XNMALLOC (n, void *);
+
+ size_t pagesize = getpagesize ();
+
+ int i;
+ for (i = 0; i < n; i++)
+ pages[i] = pagealign_xalloc (pagesize);
+
+ /* Free half of the pages in allocation order and the other half of the pages
+ in reverse allocation order. */
+ int half = n / 2;
+ for (i = 0; i < half; i++)
+ pagealign_free (pages[i]);
+ for (i = n - 1; i >= half; i--)
+ pagealign_free (pages[i]);
+
+ return 0;
+}
--
2.50.1
>From bcc735c1ebb0ae26bff284a4b48500f241db8ee4 Mon Sep 17 00:00:00 2001
From: Bruno Haible <[email protected]>
Date: Sun, 14 Sep 2025 00:25:21 +0200
Subject: [PATCH 4/4] pagealign_alloc: Don't waste large amounts of memory
(regr. 2025-09-10).
* lib/pagealign_alloc.c (get_default_impl): Choose a default that does
not waste large amounts of memory.
---
ChangeLog | 6 ++++++
lib/pagealign_alloc.c | 38 +++++++++++++++++++++++++++++++++++++-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/ChangeLog b/ChangeLog
index 78a8111497..41303f9ce2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2025-09-13 Bruno Haible <[email protected]>
+
+ pagealign_alloc: Don't waste large amounts of memory (regr. 2025-09-10).
+ * lib/pagealign_alloc.c (get_default_impl): Choose a default that does
+ not waste large amounts of memory.
+
2025-09-13 Bruno Haible <[email protected]>
pagealign_alloc: Add unit test for scalability.
diff --git a/lib/pagealign_alloc.c b/lib/pagealign_alloc.c
index 332306bd41..b730cceabd 100644
--- a/lib/pagealign_alloc.c
+++ b/lib/pagealign_alloc.c
@@ -70,11 +70,47 @@ static gl_map_t page_info_map;
static pagealign_impl_t
get_default_impl (void)
{
-#if HAVE_POSIX_MEMALIGN
+ /* The default is chosen so as to
+ 1. (most important) not waste memory, when possible.
+ 2. when there is no waste of memory, avoid using the page_info_map,
+ when possible.
+ Regarding the amount of used memory, it was determined through the
+ 'bench-pagealign_alloc' program. The results were:
+
+ b c d e f
+
+ glibc 176% 101% 175% --- ---
+ musl libc 198% 101% 197% --- ---
+ macOS 190% 100% 195% --- ---
+ FreeBSD 190% 100% 380% --- ---
+ NetBSD 185% 101% 379% --- ---
+ OpenBSD 177% 101% 101% --- ---
+ AIX 177% 101% 176% --- ---
+ Solaris 11.4 176% 101% 175% --- ---
+ Cygwin 181% 100% 181% --- ---
+ native Windows 184% --- --- 180% 100%
+ Android 182% 101% 181% --- ---
+
+ where
+ b = PA_IMPL_MALLOC
+ c = PA_IMPL_MMAP
+ d = PA_IMPL_POSIX_MEMALIGN
+ e = PA_IMPL_ALIGNED_MALLOC
+ f = PA_IMPL_VIRTUAL_ALLOC
+ */
+#if defined _WIN32 && !defined __CYGWIN__
+ /* Native Windows. */
+ return PA_IMPL_VIRTUAL_ALLOC;
+#elif defined __OpenBSD__ && HAVE_POSIX_MEMALIGN
+ /* On OpenBSD, we may choose among PA_IMPL_MMAP and PA_IMPL_POSIX_MEMALIGN.
+ The latter does not need the page_info_map. */
return PA_IMPL_POSIX_MEMALIGN;
#elif HAVE_SYS_MMAN_H
+ /* On all other platforms, PA_IMPL_MMAP is the only implementation that does
+ not waste memory. */
return PA_IMPL_MMAP;
#else
+ /* Old platforms without mmap: use PA_IMPL_MALLOC. */
return PA_IMPL_MALLOC;
#endif
}
--
2.50.1