From 31bf94f87dbabbcd21c2145a7f729395acc0de6c Mon Sep 17 00:00:00 2001 From: Armaan Sandhu Date: Mon, 14 Sep 2026 09:15:14 -0400 Subject: [PATCH] siphash24: force inlining of the round gcc does not inline sipround() at -O2: its size estimate lands just over the default max-inline-insns-auto budget, so nm on libsystemd-shared.so shows a local sipround symbol and each of the ten call sites pays a call/ret plus a reload and store of the state through the pointer, around six rounds of ~14 ALU ops. clang inlines it on its own, and gcc does too at -O3 or with --param max-inline-insns-auto=30, so this is not a missed optimization to report upstream but the inliner's size heuristic doing what it is designed to do. The documented way to overrule it for one function is always_inline. The SipHash reference implementation this file is derived from inlines the round manually, by writing it as a SIPROUND macro. AMD Ryzen AI 7 350, gcc 16.2.1, -O2 -fstack-protector-strong -fno-strict-aliasing, median of five runs: input before after speedup 8 B 61.3 ns 9.3 ns 6.63x 16 B 54.6 ns 10.7 ns 5.11x 24 B 50.0 ns 12.6 ns 3.98x 40 B 49.4 ns 16.4 ns 3.01x 64 B 53.2 ns 22.1 ns 2.41x 128 B 67.8 ns 36.9 ns 1.84x 512 B 157.7 ns 128.1 ns 1.23x 4096 B 992.5 ns 964.5 ns 1.03x The win is concentrated on short inputs because the bulk loop in siphash24_compress() was the one place gcc did inline the round; the prologue, the tail and all six rounds of siphash24_finalize() were not. Short inputs are also what we overwhelmingly hash: hashmap and set keys, unit names, paths, journal field names and data objects, bus match strings. clang is unaffected. Resulting hash values are unchanged; verified identical over every input length from 0 to 128 bytes, and test-siphash24, test-hash-funcs, test-hashmap and test-set pass. --- src/basic/siphash24.c | 8 ++++++-- src/fundamental/macro.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/basic/siphash24.c b/src/basic/siphash24.c index b73ede34c8..fb40f3d6bd 100644 --- a/src/basic/siphash24.c +++ b/src/basic/siphash24.c @@ -26,13 +26,17 @@ #include "string-util.h" #include "unaligned.h" -static uint64_t rotate_left(uint64_t x, uint8_t b) { +/* The round is small enough that it is worth inlining at every call site, but large enough that gcc's + * -O2 inlining budget declines to do so on its own, leaving a call/ret and a reload of the state around + * six rounds of ~14 ALU ops. clang inlines it either way, and the SipHash reference implementation this + * file is derived from inlines the round manually, by writing it as a macro. */ +static inline _always_inline_ uint64_t rotate_left(uint64_t x, uint8_t b) { assert(b < 64); return (x << b) | (x >> (64 - b)); } -static void sipround(struct siphash *state) { +static inline _always_inline_ void sipround(struct siphash *state) { assert(state); state->v0 += state->v1; diff --git a/src/fundamental/macro.h b/src/fundamental/macro.h index 970bfbcf4c..ef12a0dfba 100644 --- a/src/fundamental/macro.h +++ b/src/fundamental/macro.h @@ -76,6 +76,7 @@ #define _align_(x) __attribute__((__aligned__(x))) #define _alignas_(x) __attribute__((__aligned__(alignof(x)))) #define _alignptr_ __attribute__((__aligned__(sizeof(void *)))) +#define _always_inline_ __attribute__((__always_inline__)) #define _cleanup_(x) __attribute__((__cleanup__(x))) #define _const_ __attribute__((__const__)) #define _deprecated_ __attribute__((__deprecated__))