git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] Properly align memory allocations and temporary buffers

From
René Scharfe <l.s.r@web.de>
Date
Jan 7, 2022, 23:30 UTC
Message-ID
<f40c1b47-9aad-2dcc-ceeb-5dee2b517cd8@web.de>
In-Reply-To
<xmqqzgo76xpj.fsf@gitster.g>
Am 07.01.22 um 22:30 schrieb Junio C Hamano:
Show 11 quoted lines
> René Scharfe <l.s.r@web.de> writes:
>
>> If falling back to malloc(3) is fine in general then I wonder if we can
>> just it use always.  This would save two branches and make buffer
>> management trivial here.  How much worse is malloc(3) on platforms with
>> alloca(3)?  Do we sort lots of short lists somewhere?  In other words:
>> Does this stack allocation optimization actually make a measurable
>> difference?
>
> Well all the preimage of this came from your 04ee8b87 (compat: add
> qsort_s(), 2017-01-22), so you tell me ;-)

Right, except I stole the code almost verbatim from compat/qsort.c, which had this optimization since 43fe901b71 (compat: Add simplified merge sort implementation from glibc, 2008-02-05). :) The optimization may have raised my eyebrow a bit at the time, but not enough to come up with a meaningful benchmark..

https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/msort.c (the original) still uses alloca(3) (and more tricks), by the way; https://cgit.freebsd.org/src/tree/lib/libc/stdlib/merge.c still doesn't.

Show 80 quoted lines
>
>> Heap fragmention should not be a concern here, at least, because the
>> pattern of requesting, using and releasing a single allocation won't
>> leave holes.
>
> Sure.  Even in a multi-threaded environment that should be true.
>
> ----- >8 --------- >8 --------- >8 --------- >8 --------- >8 -----
> Subject: compat/qsort_s.c: avoid using potentially unaligned access
>
> The compatibility definition for qsort_s() uses "char buffer[1024]"
> on the stack to avoid making malloc() calls for small temporary
> space, which essentially hand-rolls alloca().
>
> But the elements of the array being sorted may have alignment needs
> more strict than what an array of bytes may have. &buf[0] may be
> word aligned, but using the address as if it stores the first
> element of an array of a struct, whose first member may need to be
> aligned on double-word boundary, would be a no-no.
>
> We could use xalloca() from git-compat-util.h, or alloca() directly
> on platforms with HAVE_ALLOCA_H, but let's try using unconditionally
> xmalloc() before we know the performance characteristics of the
> callers.
>
> It may not make much of an argument to inspect the current callers
> and say "it shouldn't matter to any of them", but anyway:
>
>  * The one in object-name.c is used to sort potential matches to a
>    given ambiguous object name prefix in the error path;
>
>  * The one in pack-write.c is done once per a pack .idx file being
>    written to create the reverse index, so (1) the cost of malloc()
>    overhead is dwarfed by the cost of the packing operation, and (2)
>    the number of entries being sorted is the number of objects in a
>    pack;
>
>  * The one in ref-filter.c is used by "branch --list", "tag --list",
>    and "for-each-ref", only once per operation.  We sort an array of
>    pointers with entries, each corresponding to a ref that is shown.
>
>  * The one in string-list.c is used by sort_string_list(), which is
>    way too generic to assume any access patterns, so it may or may
>    not matter, but I do not care too much ;-)
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  compat/qsort_s.c | 14 ++++----------
>  1 file changed, 4 insertions(+), 10 deletions(-)
>
> diff --git c/compat/qsort_s.c w/compat/qsort_s.c
> index 52d1f0a73d..0f7ff30f5f 100644
> --- c/compat/qsort_s.c
> +++ w/compat/qsort_s.c
> @@ -49,21 +49,15 @@ int git_qsort_s(void *b, size_t n, size_t s,
>  		int (*cmp)(const void *, const void *, void *), void *ctx)
>  {
>  	const size_t size = st_mult(n, s);
> -	char buf[1024];
> +	char *tmp;
>
>  	if (!n)
>  		return 0;
>  	if (!b || !cmp)
>  		return -1;
>
> -	if (size < sizeof(buf)) {
> -		/* The temporary array fits on the small on-stack buffer. */
> -		msort_with_tmp(b, n, s, cmp, buf, ctx);
> -	} else {
> -		/* It's somewhat large, so malloc it.  */
> -		char *tmp = xmalloc(size);
> -		msort_with_tmp(b, n, s, cmp, tmp, ctx);
> -		free(tmp);
> -	}
> +	tmp = xmalloc(size);
> +	msort_with_tmp(b, n, s, cmp, tmp, ctx);
> +	free(tmp);
>  	return 0;
>  }
--- >8 ---
Subject: [PATCH] stable-qsort: avoid using potentially unaligned access

Like in the previous patch for compat/qsort_s.c, remove the optimization of using an on-stack buffer to avoid small allocations. This ensures maximum alignment for the array elements and simplifies the code a bit.

The performance impact for the current callers is unlikely to be noticeable:

 * compat/mingw.c::make_environment_block() uses ALLOC_ARRAY and
   ALLOC_GROW several times already, so another allocation of up to 1KB
   should not matter much.
 * diffcore-rename.c::diffcore_rename_extended() is called once per diff
   or twice per merge, and those require allocations for each object and
   more already.
 * merge-ort.c::detect_and_process_renames() is called once per merge.
   It's responsible for the two per-merge diffcore_rename_extended()
   calls mentioned above as well, though.  So this is possibly the most
   impacted caller.  Per-object allocations are likely to dwarf the
   additional small allocations in git_stable_qsort(), though.
Signed-off-by: René Scharfe <l.s.r@web.de>
---
 stable-qsort.c | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/stable-qsort.c b/stable-qsort.c
index 6cbaf39f7b..7ff12467cd 100644
--- a/stable-qsort.c
+++ b/stable-qsort.c
@@ -48,15 +48,9 @@ void git_stable_qsort(void *b, size_t n, size_t s,
 		      int (*cmp)(const void *, const void *))
 {
 	const size_t size = st_mult(n, s);
-	char buf[1024];
-
-	if (size < sizeof(buf)) {
-		/* The temporary array fits on the small on-stack buffer. */
-		msort_with_tmp(b, n, s, cmp, buf);
-	} else {
-		/* It's somewhat large, so malloc it.  */
-		char *tmp = xmalloc(size);
-		msort_with_tmp(b, n, s, cmp, tmp);
-		free(tmp);
-	}
+	char *tmp;
+
+	tmp = xmalloc(size);
+	msort_with_tmp(b, n, s, cmp, tmp);
+	free(tmp);
 }
--
2.34.1
Previous: Junio C HamanoNext: Elijah Newren
Message 17 of 32 in “Properly align memory allocations and temporary buffers”
  1. Properly align memory allocations and temporary buffersJessica Clarke, Jan 5, 2022
  2. Taylor BlauJan 6, 2022
  3. Jessica ClarkeJan 6, 2022
  4. Junio C HamanoJan 6, 2022
  5. Jessica ClarkeJan 6, 2022
  6. Junio C HamanoJan 7, 2022
  7. Jessica ClarkeJan 7, 2022
  8. brian m. carlsonJan 7, 2022
  9. Jessica ClarkeJan 7, 2022
  10. brian m. carlsonJan 7, 2022
  11. Jessica ClarkeJan 7, 2022
  12. Jessica ClarkeJan 7, 2022
  13. Junio C HamanoJan 7, 2022
  14. Jessica ClarkeJan 7, 2022
  15. René ScharfeJan 7, 2022
  16. Junio C HamanoJan 7, 2022
  17. René ScharfeJan 7, 2022
  18. Elijah NewrenJan 8, 2022
  19. brian m. carlsonJan 6, 2022
  20. Jessica ClarkeJan 6, 2022
  21. Philip OakleyJan 7, 2022
  22. René ScharfeJan 7, 2022
  23. Jessica ClarkeJan 7, 2022
  24. Jessica ClarkeJan 12, 2022
  25. René ScharfeJan 12, 2022
  26. Jessica ClarkeJan 12, 2022
  27. mem-pool: Don't assume uintmax_t is aligned enough for all typesJessica Clarke, Jan 23, 2022
  28. Junio C HamanoJan 23, 2022
  29. Jessica ClarkeJan 23, 2022
  30. Junio C HamanoJan 23, 2022
  31. mem-pool: Don't assume uintmax_t is aligned enough for all typesJessica Clarke, Jan 23, 2022
  32. Junio C HamanoJan 24, 2022

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.