feat(cli): move adaptive decisions into Rust

Move the scalar runtime --adapt predicates and compression-level normalization
into Rust. Rust now decides output blocking, output backlog, input starvation,
blocked-input speedups, and bounded slower/faster level changes through an
explicit projection.

Keep the CLI iteration order, refresh clock, frame-progression snapshots,
private FIO/ZSTD contexts, diagnostics, and ZSTD_CCtx_setParameter() mutation
in C. The new C/Rust layout assertions make the scalar policy ABI explicit
without exposing those private layouts.

Test Plan:
- focused adaptive-policy tests: 6 passed
- cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1:
  533 passed
- cargo test --manifest-path rust/cli/Cargo.toml --all-targets --
  --test-threads=1: 169 passed
- legacy compression/decompression/dictionary-builder feature matrix:
  588 passed
- six library/CLI clippy gates with -D warnings
- capped serial native library/program rebuilds, 41 CLI tests, Rust library
  smoke, and full test-zstd round trips including --adapt cases
- capped serial stress gates: 278 fuzzer cases, 84+129+143 zstream cases,
  and 1,601 decode-corpus cases
- every heavyweight command used CARGO_BUILD_JOBS=1 or make -j1 and
  ulimit -v 41943040; no worker/native process remained afterward

Commit is intentionally unsigned because GPG pinentry hangs in this
non-interactive environment.
This commit is contained in:
2026-07-19 10:12:50 +02:00
parent 3b989ab3ee
commit b048937992
2 changed files with 411 additions and 23 deletions
+103 -20
View File
@@ -1968,6 +1968,68 @@ typedef enum {
FIO_rust_zstd_faster
} FIO_rust_zstd_speed_change_e;
/* Rust owns only the scalar adaptive predicates. Keep the FIO context,
* preference, and ZSTD progression layouts private to this translation unit. */
typedef struct {
U64 consumed;
U64 previousConsumed;
unsigned nbActiveWorkers;
U64 newlyProduced;
U64 newlyFlushed;
unsigned flushWaiting;
unsigned inputBlocked;
unsigned inputPresented;
U64 newlyIngested;
U64 newlyConsumed;
int compressionLevel;
int minAdaptLevel;
int maxAdaptLevel;
int maxCLevel;
} FIO_rust_zstd_adapt_projection_t;
typedef char FIO_rust_zstd_adapt_projection_layout[
(offsetof(FIO_rust_zstd_adapt_projection_t, consumed) == 0
&& offsetof(FIO_rust_zstd_adapt_projection_t, previousConsumed)
== sizeof(U64)
&& offsetof(FIO_rust_zstd_adapt_projection_t, nbActiveWorkers)
== 2 * sizeof(U64)
&& offsetof(FIO_rust_zstd_adapt_projection_t, newlyProduced)
== (sizeof(void*) == 8 ? 24 : 20)
&& offsetof(FIO_rust_zstd_adapt_projection_t, newlyFlushed)
== (sizeof(void*) == 8 ? 32 : 28)
&& offsetof(FIO_rust_zstd_adapt_projection_t, flushWaiting)
== (sizeof(void*) == 8 ? 40 : 36)
&& offsetof(FIO_rust_zstd_adapt_projection_t, inputBlocked)
== (sizeof(void*) == 8 ? 44 : 40)
&& offsetof(FIO_rust_zstd_adapt_projection_t, inputPresented)
== (sizeof(void*) == 8 ? 48 : 44)
&& offsetof(FIO_rust_zstd_adapt_projection_t, newlyIngested)
== (sizeof(void*) == 8 ? 56 : 48)
&& offsetof(FIO_rust_zstd_adapt_projection_t, newlyConsumed)
== (sizeof(void*) == 8 ? 64 : 56)
&& offsetof(FIO_rust_zstd_adapt_projection_t, compressionLevel)
== (sizeof(void*) == 8 ? 72 : 64)
&& offsetof(FIO_rust_zstd_adapt_projection_t, minAdaptLevel)
== (sizeof(void*) == 8 ? 76 : 68)
&& offsetof(FIO_rust_zstd_adapt_projection_t, maxAdaptLevel)
== (sizeof(void*) == 8 ? 80 : 72)
&& offsetof(FIO_rust_zstd_adapt_projection_t, maxCLevel)
== (sizeof(void*) == 8 ? 84 : 76)
&& sizeof(FIO_rust_zstd_adapt_projection_t)
== (sizeof(void*) == 8 ? 88 : 80))
? 1 : -1];
enum {
FIO_RUST_ZSTD_ADAPT_OUTPUT_BLOCKED,
FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG,
FIO_RUST_ZSTD_ADAPT_INPUT_STARVATION,
FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT,
FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER,
FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER
};
int FIO_rust_zstd_adapt(
int policy, const FIO_rust_zstd_adapt_projection_t* projection);
typedef struct {
FIO_ctx_t* fCtx;
FIO_prefs_t* prefs;
@@ -2070,13 +2132,14 @@ static void FIO_rust_zstd_iteration(void* opaque, const char* srcFileName,
(FIO_rust_zstd_projection_context_t*)opaque;
FIO_prefs_t* const prefs = context->prefs;
FIO_ctx_t* const fCtx = context->fCtx;
FIO_rust_zstd_adapt_projection_t adaptProjection;
context->inputPresented++;
if (oldInputPos == newInputPos) context->inputBlocked++;
if (!toFlushNow) context->flushWaiting = 1;
/* Adaptive mode remains in C so its policy and private preference/context
* access stay identical while Rust owns the surrounding stream loop. */
/* C retains the clock gate, progression snapshots, diagnostics, and
* private context mutations; Rust supplies only scalar policy decisions. */
if (prefs->adaptiveMode &&
UTIL_clockSpanMicro(context->lastAdaptTime) > REFRESH_RATE) {
ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(context->cctx);
@@ -2096,16 +2159,25 @@ static void FIO_rust_zstd_iteration(void* opaque, const char* srcFileName,
* either because output is slow and all buffers are full
* or because input is slow and no job can start while waiting for at least one buffer to be filled.
* note : exclude starting part, since currentJobID > 1 */
if ((zfp.consumed == context->previousZfpUpdate.consumed)
&& (zfp.nbActiveWorkers == 0)) {
memset(&adaptProjection, 0, sizeof(adaptProjection));
adaptProjection.consumed = zfp.consumed;
adaptProjection.previousConsumed = context->previousZfpUpdate.consumed;
adaptProjection.nbActiveWorkers = zfp.nbActiveWorkers;
if (FIO_rust_zstd_adapt(
FIO_RUST_ZSTD_ADAPT_OUTPUT_BLOCKED, &adaptProjection)
== FIO_rust_zstd_slower) {
DISPLAYLEVEL(6, "all buffers full : compression stopped => slow down \n")
context->speedChange = FIO_rust_zstd_slower;
}
context->previousZfpUpdate = zfp;
if ((newlyProduced > (newlyFlushed * 9 / 8))
&& (context->flushWaiting == 0)) {
adaptProjection.newlyProduced = newlyProduced;
adaptProjection.newlyFlushed = newlyFlushed;
adaptProjection.flushWaiting = context->flushWaiting;
if (FIO_rust_zstd_adapt(
FIO_RUST_ZSTD_ADAPT_OUTPUT_BACKLOG, &adaptProjection)
== FIO_rust_zstd_slower) {
DISPLAYLEVEL(6, "compression faster than flush (%llu > %llu), and flushed was never slowed down by lack of production => slow down \n",
newlyProduced, newlyFlushed);
context->speedChange = FIO_rust_zstd_slower;
@@ -2119,7 +2191,11 @@ static void FIO_rust_zstd_iteration(void* opaque, const char* srcFileName,
/* check input speed */
if (zfp.currentJobID > (unsigned)(prefs->nbWorkers+1)) {
if (context->inputBlocked <= 0) {
memset(&adaptProjection, 0, sizeof(adaptProjection));
adaptProjection.inputBlocked = context->inputBlocked;
if (FIO_rust_zstd_adapt(
FIO_RUST_ZSTD_ADAPT_INPUT_STARVATION, &adaptProjection)
== FIO_rust_zstd_slower) {
DISPLAYLEVEL(6, "input is never blocked => input is slower than ingestion \n");
context->speedChange = FIO_rust_zstd_slower;
} else if (context->speedChange == FIO_rust_zstd_noChange) {
@@ -2138,9 +2214,15 @@ static void FIO_rust_zstd_iteration(void* opaque, const char* srcFileName,
(double)context->inputBlocked/context->inputPresented*100,
(unsigned)newlyIngested, (unsigned)newlyConsumed,
(unsigned)newlyFlushed, (unsigned)newlyProduced);
if ((context->inputBlocked > context->inputPresented / 8)
&& (newlyFlushed * 33 / 32 > newlyProduced)
&& (newlyIngested * 33 / 32 > newlyConsumed)) {
adaptProjection.inputBlocked = context->inputBlocked;
adaptProjection.inputPresented = context->inputPresented;
adaptProjection.newlyIngested = newlyIngested;
adaptProjection.newlyConsumed = newlyConsumed;
adaptProjection.newlyProduced = newlyProduced;
adaptProjection.newlyFlushed = newlyFlushed;
if (FIO_rust_zstd_adapt(
FIO_RUST_ZSTD_ADAPT_BLOCKED_INPUT, &adaptProjection)
== FIO_rust_zstd_faster) {
DISPLAYLEVEL(6, "recommend faster as in(%llu) >= (%llu)comp(%llu) <= out(%llu) \n",
newlyIngested, newlyConsumed,
newlyProduced, newlyFlushed);
@@ -2153,22 +2235,23 @@ static void FIO_rust_zstd_iteration(void* opaque, const char* srcFileName,
if (context->speedChange == FIO_rust_zstd_slower) {
DISPLAYLEVEL(6, "slower speed , higher compression \n")
(*compressionLevel)++;
if (*compressionLevel > ZSTD_maxCLevel())
*compressionLevel = ZSTD_maxCLevel();
if (*compressionLevel > prefs->maxAdaptLevel)
*compressionLevel = prefs->maxAdaptLevel;
*compressionLevel += (*compressionLevel == 0);
memset(&adaptProjection, 0, sizeof(adaptProjection));
adaptProjection.compressionLevel = *compressionLevel;
adaptProjection.maxAdaptLevel = prefs->maxAdaptLevel;
adaptProjection.maxCLevel = ZSTD_maxCLevel();
*compressionLevel = FIO_rust_zstd_adapt(
FIO_RUST_ZSTD_ADAPT_LEVEL_SLOWER, &adaptProjection);
ZSTD_CCtx_setParameter(context->cctx,
ZSTD_c_compressionLevel,
*compressionLevel);
}
if (context->speedChange == FIO_rust_zstd_faster) {
DISPLAYLEVEL(6, "faster speed , lighter compression \n")
(*compressionLevel)--;
if (*compressionLevel < prefs->minAdaptLevel)
*compressionLevel = prefs->minAdaptLevel;
*compressionLevel -= (*compressionLevel == 0);
memset(&adaptProjection, 0, sizeof(adaptProjection));
adaptProjection.compressionLevel = *compressionLevel;
adaptProjection.minAdaptLevel = prefs->minAdaptLevel;
*compressionLevel = FIO_rust_zstd_adapt(
FIO_RUST_ZSTD_ADAPT_LEVEL_FASTER, &adaptProjection);
ZSTD_CCtx_setParameter(context->cctx,
ZSTD_c_compressionLevel,
*compressionLevel);