feat(cli): move benchmark bridge into Rust
Move benchmark parameter normalization and dispatch into the Rust CLI archive while retaining the stable C launcher entry point. Keep compact CLI variants benchmark-free and add an explicit helpers feature for the C test generators. Test Plan: - cargo test --manifest-path rust/cli/Cargo.toml --no-default-features --features benchmark - cargo test --manifest-path rust/cli/Cargo.toml --no-default-features --features helpers - cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets --no-default-features --features cli,compression,decompression,benchmark - cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets --no-default-features --features helpers - make -B -C programs -j2 zstd zstd-small zstd-frugal - make -B -C tests -j2 test-cli-tests - programs/zstd -b1i1 -q
This commit is contained in:
+6
-1
@@ -7,13 +7,18 @@ edition = "2021"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[features]
|
||||
default = ["cli", "compression", "decompression"]
|
||||
default = ["cli", "compression", "decompression", "benchmark"]
|
||||
# The command-line parser and dispatch layer, which requires the C fileio
|
||||
# backend at link time. Program archives enable it; C test binaries link a
|
||||
# helpers-only archive (timefn) built without it.
|
||||
cli = []
|
||||
compression = []
|
||||
decompression = []
|
||||
helpers = []
|
||||
# The integrated benchmark archive is optional so zstd-small and zstd-frugal
|
||||
# can omit every benchmark implementation and its timing/data-generator
|
||||
# dependencies.
|
||||
benchmark = ["compression"]
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
+4
-1
@@ -1,8 +1,10 @@
|
||||
#[cfg(any(feature = "benchmark", feature = "helpers"))]
|
||||
#[path = "../../src/benchfn.rs"]
|
||||
mod benchfn;
|
||||
#[cfg(feature = "cli")]
|
||||
#[cfg(all(feature = "cli", feature = "benchmark"))]
|
||||
#[path = "../../src/benchzstd.rs"]
|
||||
mod benchzstd;
|
||||
#[cfg(any(feature = "benchmark", feature = "helpers"))]
|
||||
#[path = "../../src/datagen.rs"]
|
||||
mod datagen;
|
||||
#[cfg(feature = "cli")]
|
||||
@@ -11,6 +13,7 @@ mod dibio;
|
||||
#[cfg(feature = "cli")]
|
||||
#[path = "../../src/fileio_prefs.rs"]
|
||||
mod fileio_prefs;
|
||||
#[cfg(any(feature = "benchmark", feature = "helpers"))]
|
||||
#[path = "../../src/lorem.rs"]
|
||||
mod lorem;
|
||||
#[path = "../../src/timefn.rs"]
|
||||
|
||||
+162
-25
@@ -10,11 +10,11 @@
|
||||
//! writes, dictionary loading, streaming, and metadata preservation remain in
|
||||
//! `programs/fileio.c` for this first migration step.
|
||||
//!
|
||||
//! Benchmark mode (`-b`) parses here and dispatches through the
|
||||
//! `ZSTD_rust_cli_bench` bridge in `programs/zstdcli.c`: the run/timing loop
|
||||
//! (benchfn, timefn) is Rust, while orchestration and result formatting
|
||||
//! (`benchzstd.c`) remain C behind the preprocessor-gated bridge, so builds
|
||||
//! with `ZSTD_NOBENCH` never reference benchmark symbols.
|
||||
//! Benchmark mode (`-b`) parses and dispatches through the Rust CLI archive.
|
||||
//! The bridge preserves the C CLI's argument normalization and uses the
|
||||
//! existing Rust `benchzstd` APIs. Builds without the `benchmark` feature
|
||||
//! return the same unavailable-build result as `ZSTD_NOBENCH` builds without
|
||||
//! referencing benchmark symbols.
|
||||
//!
|
||||
//! Recursive expansion (`-r`), `--filelist`, the output-directory modes, and
|
||||
//! `--list` reuse the C `FileNamesTable` helpers from `programs/util.c` and
|
||||
@@ -104,7 +104,7 @@ struct FIO_ctx_t {
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
struct ZSTD_compressionParameters {
|
||||
pub(crate) struct ZSTD_compressionParameters {
|
||||
windowLog: u32,
|
||||
chainLog: u32,
|
||||
hashLog: u32,
|
||||
@@ -295,22 +295,6 @@ unsafe extern "C" {
|
||||
display_level: c_int,
|
||||
) -> c_int;
|
||||
|
||||
/// Benchmark bridge implemented by the `programs/zstdcli.c` shim, which
|
||||
/// owns the `ZSTD_NOBENCH` preprocessor decision. Returns the benchmark
|
||||
/// result (>= 0), or -1 when benchmarking is compiled out.
|
||||
fn ZSTD_rust_cli_bench(
|
||||
file_names: *const *const c_char,
|
||||
nb_files: c_uint,
|
||||
dict_file_name: *const c_char,
|
||||
start_level: c_int,
|
||||
end_level: c_int,
|
||||
compression_params: *const ZSTD_compressionParameters,
|
||||
display_level: c_int,
|
||||
nb_seconds: c_uint,
|
||||
block_size: usize,
|
||||
nb_workers: c_int,
|
||||
mode: c_int,
|
||||
) -> c_int;
|
||||
/// Narrow bridge to `programs/dibio.c`. The file loader and dictionary
|
||||
/// algorithms remain on the C/Rust library side of this boundary.
|
||||
#[cfg(feature = "compression")]
|
||||
@@ -2374,9 +2358,122 @@ fn run_list(cli: &Cli) -> i32 {
|
||||
unsafe { FIO_listMultipleFiles(inputs.len() as c_uint, inputs.as_ptr(), cli.display_level) }
|
||||
}
|
||||
|
||||
/// Runs benchmark mode through the C bridge. Level clamping against
|
||||
/// `ZSTD_maxCLevel()` happens on the C side, where the symbol is always
|
||||
/// available when benchmarking is compiled in. No input file means a
|
||||
#[cfg(feature = "benchmark")]
|
||||
fn normalize_benchmark_levels(
|
||||
start_c_level: c_int,
|
||||
end_c_level: c_int,
|
||||
mode: c_int,
|
||||
max_c_level: c_int,
|
||||
) -> (c_int, c_int) {
|
||||
let mut start_level = start_c_level;
|
||||
let mut end_level = end_c_level;
|
||||
if mode == crate::benchzstd::BMK_decodeOnly {
|
||||
start_level = 0;
|
||||
end_level = 0;
|
||||
}
|
||||
if start_level > max_c_level {
|
||||
start_level = max_c_level;
|
||||
}
|
||||
if end_level > max_c_level {
|
||||
end_level = max_c_level;
|
||||
}
|
||||
if end_level < start_level {
|
||||
end_level = start_level;
|
||||
}
|
||||
(start_level, end_level)
|
||||
}
|
||||
|
||||
/// Benchmark bridge formerly implemented by `programs/zstdcli.c`.
|
||||
///
|
||||
/// Keep the exported name because it is a useful program-side ABI boundary,
|
||||
/// but make the Rust CLI archive own both the normalization and dispatch.
|
||||
#[cfg(feature = "benchmark")]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_cli_bench(
|
||||
file_names: *const *const c_char,
|
||||
nb_files: c_uint,
|
||||
dict_file_name: *const c_char,
|
||||
start_c_level: c_int,
|
||||
end_c_level: c_int,
|
||||
compression_params: *const ZSTD_compressionParameters,
|
||||
display_level: c_int,
|
||||
nb_seconds: c_uint,
|
||||
block_size: usize,
|
||||
nb_workers: c_int,
|
||||
mode: c_int,
|
||||
) -> c_int {
|
||||
let mut advanced_params = crate::benchzstd::BMK_initAdvancedParams();
|
||||
advanced_params.nbSeconds = nb_seconds;
|
||||
advanced_params.blockSize = block_size;
|
||||
advanced_params.nbWorkers = nb_workers;
|
||||
advanced_params.mode = mode;
|
||||
|
||||
let (start_level, end_level) =
|
||||
normalize_benchmark_levels(start_c_level, end_c_level, mode, unsafe {
|
||||
ZSTD_maxCLevel()
|
||||
});
|
||||
let compression_params = compression_params.cast();
|
||||
if nb_files == 0 {
|
||||
/* No input file: benchmark a synthetic sample (lorem generator). */
|
||||
unsafe {
|
||||
crate::benchzstd::BMK_syntheticTest(
|
||||
-1.0,
|
||||
start_level,
|
||||
end_level,
|
||||
compression_params,
|
||||
display_level,
|
||||
&advanced_params,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
crate::benchzstd::BMK_benchFilesAdvanced(
|
||||
file_names,
|
||||
nb_files,
|
||||
dict_file_name,
|
||||
start_level,
|
||||
end_level,
|
||||
compression_params,
|
||||
display_level,
|
||||
&advanced_params,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `ZSTD_NOBENCH` compatibility bridge for compact CLI archives.
|
||||
#[cfg(not(feature = "benchmark"))]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_cli_bench(
|
||||
file_names: *const *const c_char,
|
||||
nb_files: c_uint,
|
||||
dict_file_name: *const c_char,
|
||||
start_c_level: c_int,
|
||||
end_c_level: c_int,
|
||||
compression_params: *const ZSTD_compressionParameters,
|
||||
display_level: c_int,
|
||||
nb_seconds: c_uint,
|
||||
block_size: usize,
|
||||
nb_workers: c_int,
|
||||
mode: c_int,
|
||||
) -> c_int {
|
||||
let _ = (
|
||||
file_names,
|
||||
nb_files,
|
||||
dict_file_name,
|
||||
start_c_level,
|
||||
end_c_level,
|
||||
compression_params,
|
||||
display_level,
|
||||
nb_seconds,
|
||||
block_size,
|
||||
nb_workers,
|
||||
mode,
|
||||
);
|
||||
-1
|
||||
}
|
||||
|
||||
/// Runs benchmark mode through the Rust bridge. No input file means a
|
||||
/// synthetic-sample benchmark, matching the C CLI.
|
||||
fn run_bench(cli: &Cli) -> Result<i32, String> {
|
||||
let inputs: Vec<*const c_char> = cli.inputs.iter().map(|value| value.as_ptr()).collect();
|
||||
@@ -3359,4 +3456,44 @@ mod tests {
|
||||
assert_eq!(cli.operation, Operation::Bench);
|
||||
assert!(cli.bench_decode);
|
||||
}
|
||||
|
||||
#[cfg(feature = "benchmark")]
|
||||
#[test]
|
||||
fn benchmark_level_normalization_matches_c_bridge() {
|
||||
assert_eq!(
|
||||
normalize_benchmark_levels(100, 4, crate::benchzstd::BMK_both, 22),
|
||||
(22, 22)
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_benchmark_levels(7, 3, crate::benchzstd::BMK_both, 22),
|
||||
(7, 7)
|
||||
);
|
||||
assert_eq!(
|
||||
normalize_benchmark_levels(7, 3, crate::benchzstd::BMK_decodeOnly, 22),
|
||||
(0, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "benchmark"))]
|
||||
#[test]
|
||||
fn benchmark_bridge_returns_unavailable_without_feature() {
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
ZSTD_rust_cli_bench(
|
||||
ptr::null(),
|
||||
0,
|
||||
ptr::null(),
|
||||
3,
|
||||
3,
|
||||
ptr::null(),
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
)
|
||||
},
|
||||
-1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user