Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 24 additions & 35 deletions .github/workflows/ci-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,64 +131,53 @@ jobs:
if: runner.os == 'Linux' && matrix.compiler == 'gcc'
continue-on-error: true
run: |
rm -rf build-rel build-rel-o3
meson setup build-rel-o3 --buildtype=release \
-Doptimization=3 -Dcompact_release=false
meson compile -C build-rel-o3
rm -rf build-rel
meson setup build-rel --buildtype=release
meson compile -C build-rel

mapfile -t control_shared < <(find build-rel-o3 -maxdepth 1 -type f \
mapfile -t release_shared < <(find build-rel -maxdepth 1 -type f \
-name 'libchronoid.so.*' -print)
mapfile -t compact_shared < <(find build-rel -maxdepth 1 -type f \
-name 'libchronoid.so.*' -print)
if [ "${#control_shared[@]}" -ne 1 ] \
|| [ "${#compact_shared[@]}" -ne 1 ]; then
echo "Expected one versioned shared library per build." >&2
if [ "${#release_shared[@]}" -ne 1 ]; then
echo "Expected one versioned shared library." >&2
exit 1
fi

strip --strip-unneeded "${control_shared[0]}"
strip --strip-unneeded "${compact_shared[0]}"
strip --strip-unneeded "${release_shared[0]}"

{
echo '## Footprint — ${{ matrix.os }} / GCC / x86_64'
echo '## Standard O3 footprint — ${{ matrix.os }} / GCC / ${{ runner.arch }}'
echo ''
echo 'Shared libraries are stripped with `strip --strip-unneeded`;'
echo 'the static archive and CLI are measured as built.'
echo ''
echo '| Artifact | O3 control | Compact O2 | Delta | Reduction |'
echo '|---|---:|---:|---:|---:|'
failed=0
echo '| Artifact | Bytes |'
echo '|---|---:|'
for spec in \
"shared:${control_shared[0]}:${compact_shared[0]}" \
'static:build-rel-o3/libchronoid.a:build-rel/libchronoid.a' \
'chronoid-gen:build-rel-o3/chronoid-gen:build-rel/chronoid-gen'; do
IFS=: read -r label control_path compact_path <<< "$spec"
if [ ! -f "$control_path" ] || [ ! -f "$compact_path" ]; then
"shared:${release_shared[0]}" \
'static:build-rel/libchronoid.a' \
'chronoid-gen:build-rel/chronoid-gen'; do
IFS=: read -r label artifact_path <<< "$spec"
if [ ! -f "$artifact_path" ]; then
echo "Missing footprint artifact for $label." >&2
exit 1
fi
control_bytes=$(stat -c '%s' "$control_path")
compact_bytes=$(stat -c '%s' "$compact_path")
case "$control_bytes:$compact_bytes" in
*[!0-9:]*|'':*|*:'')
artifact_bytes=$(stat -c '%s' "$artifact_path")
case "$artifact_bytes" in
*[!0-9]*|'')
echo "Invalid footprint size for $label." >&2
exit 1
;;
esac
delta=$((compact_bytes - control_bytes))
reduction=$(awk -v c="$control_bytes" -v n="$compact_bytes" \
'BEGIN { printf "%.1f%%", 100 * (c - n) / c }')
printf '| %s | %s | %s | %+d | %s |\n' \
"$label" "$control_bytes" "$compact_bytes" "$delta" "$reduction"
if [ "$compact_bytes" -ge "$control_bytes" ]; then
echo "Compact release did not shrink $label." >&2
failed=1
fi
printf '| %s | %s |\n' "$label" "$artifact_bytes"
done
echo ''
echo '<details><summary>Shared-library sections</summary>'
echo ''
echo '```text'
size -A "${release_shared[0]}"
echo '```'
echo '</details>'
} >> "$GITHUB_STEP_SUMMARY"
exit "$failed"

- name: Publish results
if: always()
Expand Down
18 changes: 8 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@ every binary-incompatible change, and the major version bumps with it.

### Changed (build)

- Release builds now compile production targets at optimization level 2 by
default. On GCC 16.2.1 x86_64 this reduces the stripped shared library from
43,168 to 39,072 bytes (-9.5%), the as-built static archive from 56,142 to
52,358 bytes (-6.7%), and the as-built CLI from 37,312 to 33,328 bytes
(-10.7%) relative to an otherwise identical O3 build. Size optimization was
smaller but roughly halved KSUID CLI throughput, while LTO enlarged the
static archive more than threefold, so O2 is the size/performance compromise.
Pass `-Dcompact_release=false` to remove the per-target override and honor
the caller's global optimization setting. Public API, ABI, wire formats,
SIMD dispatch, and runtime dependencies are unchanged.
- Release builds again honor Meson's standard optimization level 3. A private
RNG-state observation helper now uses its fixed test-buffer contract instead
of a variable-length copy, avoiding substantial compiler expansion. On GCC
16.2.1 x86_64 this reduces the stripped shared library from 43,168 to 39,072
bytes (-9.5%), the as-built static archive from 56,142 to 55,532 bytes
(-1.1%), and the as-built CLI from 37,312 to 37,248 bytes (-0.2%) versus the
same O3 build before the source change. Production RNG paths, public API,
ABI, wire formats, SIMD dispatch, and runtime dependencies are unchanged.

### Fixed (build)

Expand Down
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,27 @@ meson compile -C build-release
strip --strip-unneeded build-release/libchronoid.so.*
```

Release builds use Meson optimization level 2 by default. This keeps the
installed artifacts compact without the substantial KSUID throughput loss
observed with size optimization. To honor Meson's or the caller's global
release optimization level instead, configure with
`-Dcompact_release=false` (Meson's standard release default is level 3).
Release builds use Meson's standard optimization level 3. Binary footprint is
reduced in the source rather than by overriding the caller's optimization
policy.

## Footprint

A GCC 16.2.1 release build on x86_64 produces the following paired result.
A GCC 16.2.1 O3 release build on x86_64 produces the following paired result.
The shared library is measured after `strip --strip-unneeded`; the static
archive and CLI are measured as built. Both columns use the same compiler and
linker invocation, with only `compact_release` changed:

| Artifact | Compact O2 | O3 control | Reduction |
| :-------------------- | ---------: | ---------: | --------: |
| libchronoid.so.1.1.0 | 39 072 | 43 168 | 9.5% |
| libchronoid.a | 52 358 | 56 142 | 6.7% |
| chronoid-gen (CLI) | 33 328 | 37 312 | 10.7% |
archive and CLI are measured as built. Both columns use the same compiler,
linker, build options, and optimization level:

| Artifact | O3 before | O3 source-optimized | Reduction |
| :-------------------- | --------: | ------------------: | --------: |
| libchronoid.so.1.1.0 | 43 168 | 39 072 | 9.5% |
| libchronoid.a | 56 142 | 55 532 | 1.1% |
| chronoid-gen (CLI) | 37 312 | 37 248 | 0.2% |

The source change compacts a private RNG-state observation helper used only by
the test suite. Its documented fixed-capacity contract avoids the compiler's
large variable-length `memcpy` expansion without changing production RNG hot
paths, public API, or wire formats.

The bulk-encode AVX2 kernel from `chronoid/ksuid/encode_avx2.c` accounts for
roughly 8 KB of the shared-library size, and the UUIDv7 hex AVX2
Expand Down
13 changes: 5 additions & 8 deletions chronoid/rand.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,12 @@ extern _Atomic int chronoid_thread_exit_wipes_observed;
* erase. Must be called before any draw on the same thread. */
void chronoid_random_thread_state_set_sentinel_for_testing (void);

/* Copy the calling thread's TLS RNG state bytes into |out| (which
* must be at least sizeof(chronoid_tls_rng_t) -- the test is allowed to
* over-allocate). Used to assert the wipe actually zeroed the
* region. */
void chronoid_random_thread_state_peek_for_testing (uint8_t * out,
size_t out_len);
/* Capacity for the private TLS-state observation buffer. The helper
* returns the actual bytes copied; any unused tail stays untouched. */
#define CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY 256u

/* Size in bytes that a peek buffer must accommodate. */
size_t chronoid_random_thread_state_size_for_testing (void);
size_t chronoid_random_thread_state_peek_for_testing (uint8_t
out[CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY]);

/* Test-only: replace the wall-clock source used by chronoid_uuidv7_*
* generation. Pass a non-NULL fn to install; pass NULL to restore the
Expand Down
17 changes: 7 additions & 10 deletions chronoid/rand_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ typedef struct
bool destructor_registered; /* thread-exit wipe registered yet? */
} chronoid_tls_rng_t;

_Static_assert (sizeof (chronoid_tls_rng_t)
<= CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY,
"test observation capacity must hold the complete TLS RNG state");

static _Thread_local chronoid_tls_rng_t chronoid_tls_rng_;

/* Re-entry guard for chronoid_random_thread_state_wipe. A future change
Expand Down Expand Up @@ -120,18 +124,11 @@ chronoid_random_thread_state_set_sentinel_for_testing (void)
chronoid_tls_rng_.destructor_registered = registered;
}

void
chronoid_random_thread_state_peek_for_testing (uint8_t *out, size_t out_len)
{
size_t n = sizeof chronoid_tls_rng_;
if (out_len < n)
n = out_len;
memcpy (out, &chronoid_tls_rng_, n);
}

size_t
chronoid_random_thread_state_size_for_testing (void)
chronoid_random_thread_state_peek_for_testing (uint8_t
out[CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY])
{
memcpy (out, &chronoid_tls_rng_, sizeof chronoid_tls_rng_);
return sizeof chronoid_tls_rng_;
}

Expand Down
16 changes: 0 additions & 16 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,6 @@ project('libchronoid', 'c',
cc = meson.get_compiler('c')
pkg = import('pkgconfig')

# Meson's release build type selects optimization level 3. GCC and Clang
# expand several of our fixed-trip-count codec loops aggressively at that
# level, increasing all three installed artifacts without a measurable CLI
# throughput benefit. Keep the public release recipe compact by overriding
# production targets to level 2. Setting -Dcompact_release=false removes the
# target override and honors the caller's global optimization choice.
compact_release_options = []
if get_option('compact_release') and get_option('buildtype') == 'release'
compact_release_options = ['optimization=2']
endif

# Hardening / portability flags applied to all our TUs. Compiler-
# specific defaults split between gcc/clang and MSVC: visibility
# control + POSIX feature macros only mean anything on the GCC-style
Expand Down Expand Up @@ -302,7 +291,6 @@ if have_hex_ssse3
'chronoid/uuidv7/hex_ssse3.c',
include_directories : inc,
c_args : ssse3_arg != '' ? [ssse3_arg] : [],
override_options : compact_release_options,
pic : true,
install : false,
)
Expand All @@ -317,7 +305,6 @@ if have_avx2_batch
'chronoid/ksuid/encode_avx2.c',
include_directories : inc,
c_args : [avx2_arg],
override_options : compact_release_options,
pic : true,
install : false,
)
Expand All @@ -341,7 +328,6 @@ if have_hex_avx2
'chronoid/uuidv7/hex_avx2.c',
include_directories : inc,
c_args : [avx2_arg],
override_options : compact_release_options,
pic : true,
install : false,
)
Expand All @@ -352,7 +338,6 @@ chronoid_lib = both_libraries('chronoid', core_sources,
include_directories : inc,
dependencies : extra_link_deps,
link_whole : hex_ssse3_lib_dep + avx2_lib_dep + hex_avx2_lib_dep,
override_options : compact_release_options,
version : meson.project_version(),
# soversion tracks semver major: a major bump means a binary-
# incompatible ABI break and the SONAME flips with it. 1.0 is the
Expand Down Expand Up @@ -386,7 +371,6 @@ if get_option('cli')
'examples/chronoid-gen.c',
include_directories : inc,
link_with : chronoid_lib.get_static_lib(),
override_options : compact_release_options,
install : true,
)
endif
Expand Down
2 changes: 0 additions & 2 deletions meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ option('tests', type : 'boolean', value : true,
description : 'Build the unit test suite')
option('cli', type : 'boolean', value : true,
description : 'Build the chronoid-gen CLI tool')
option('compact_release', type : 'boolean', value : true,
description : 'Use optimization level 2 for smaller release artifacts; disable to honor the global release optimization level')
option('simd', type : 'combo', choices : ['auto', 'none'], value : 'auto',
description : 'Enable SIMD/NEON acceleration (auto-detected per host arch)')
option('avx2_batch', type : 'feature', value : 'auto',
Expand Down
27 changes: 20 additions & 7 deletions tests/test_rand_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,30 @@ sentinel_thread_body (void *opaque)
* registered destructor still fires at thread exit and wipes
* whatever is in the slot. */
chronoid_random_thread_state_set_sentinel_for_testing ();
/* Sanity check: the sentinel landed in the slot. */
size_t n = chronoid_random_thread_state_size_for_testing ();
uint8_t *peek = malloc (n);
if (peek == NULL)
return -1;
chronoid_random_thread_state_peek_for_testing (peek, n);
/* Sanity check: the sentinel landed in the slot. Guard bytes and
* the unused capacity tail prove that the observation helper copies
* exactly the private state size it reports. */
enum
{ guard_size = 16 };
uint8_t guarded[guard_size + CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY
+ guard_size];
memset (guarded, 0x3c, sizeof guarded);
uint8_t *peek = guarded + guard_size;
size_t n = chronoid_random_thread_state_peek_for_testing (peek);
if (n == 0 || n > CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY)
return -3;
size_t a5_count = 0;
for (size_t i = 0; i < n; ++i)
if (peek[i] == 0xa5)
++a5_count;
free (peek);
for (size_t i = 0; i < guard_size; ++i) {
if (guarded[i] != 0x3c
|| peek[CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY + i] != 0x3c)
return -3;
}
for (size_t i = n; i < CHRONOID_RANDOM_THREAD_STATE_PEEK_CAPACITY; ++i)
if (peek[i] != 0x3c)
return -3;
/* At least 64 bytes of state[16] plus 64 bytes of buf must be
* the sentinel (the bool flags read as 1, not 0xa5, but everything
* else does). Use a conservative lower bound. */
Expand Down
Loading