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:
+12
-2
@@ -124,7 +124,7 @@ RUST_CLI_STATICLIB := $(RUST_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
|
||||
RUST_CLI_STATICLIB_32 := $(RUST_CLI_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_cli_rs.a
|
||||
RUST_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
|
||||
--target-dir $(RUST_CLI_TARGET_DIR) \
|
||||
--no-default-features --features cli,compression,decompression
|
||||
--no-default-features --features cli,compression,decompression,benchmark
|
||||
|
||||
$(RUST_CLI_STATICLIB): $(RUST_CLI_SOURCES)
|
||||
$(CARGO) build $(RUST_CLI_CARGO_FLAGS)
|
||||
@@ -132,6 +132,16 @@ $(RUST_CLI_STATICLIB): $(RUST_CLI_SOURCES)
|
||||
$(RUST_CLI_STATICLIB_32): $(RUST_CLI_SOURCES)
|
||||
$(CARGO) build $(RUST_CLI_CARGO_FLAGS) --target $(RUST_TARGET_32)
|
||||
|
||||
RUST_NOBENCH_CLI_BUILD_CONFIG := cli-c1-d1-nobench-$(RUST_HUF_MODE)
|
||||
RUST_NOBENCH_CLI_TARGET_DIR := $(RUST_DIR)/target/$(RUST_NOBENCH_CLI_BUILD_CONFIG)
|
||||
RUST_NOBENCH_CLI_STATICLIB := $(RUST_NOBENCH_CLI_TARGET_DIR)/release/libzstd_cli_rs.a
|
||||
RUST_NOBENCH_CLI_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
|
||||
--target-dir $(RUST_NOBENCH_CLI_TARGET_DIR) \
|
||||
--no-default-features --features cli,compression,decompression
|
||||
|
||||
$(RUST_NOBENCH_CLI_STATICLIB): $(RUST_CLI_SOURCES)
|
||||
$(CARGO) build $(RUST_NOBENCH_CLI_CARGO_FLAGS)
|
||||
|
||||
RUST_DECOMPRESS_BUILD_CONFIG := lib-c0-d1-b0-$(RUST_HUF_MODE)-$(RUST_DEBUG_MODE)
|
||||
RUST_DECOMPRESS_TARGET_DIR := $(RUST_DIR)/target/$(RUST_DECOMPRESS_BUILD_CONFIG)
|
||||
RUST_DECOMPRESS_STATICLIB := $(RUST_DECOMPRESS_TARGET_DIR)/release/libzstd_rs.a
|
||||
@@ -455,7 +465,7 @@ CLEAN += zstd-small zstd-frugal
|
||||
# requested HUF decoder mode from the C sources while the Rust archive sees it.
|
||||
ZSTD_SMALL_HUF_CFLAGS := $(filter -DHUF_FORCE_DECOMPRESS_X1% -DHUF_FORCE_DECOMPRESS_X2%,$(CFLAGS))
|
||||
zstd-small: CFLAGS = -Os -Wl,-s $(ZSTD_SMALL_HUF_CFLAGS)
|
||||
zstd-frugal zstd-small: $(ZSTDLIB_CORE_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_STATICLIB) $(RUST_CLI_STATICLIB)
|
||||
zstd-frugal zstd-small: $(ZSTDLIB_CORE_SRC) zstdcli.c util.c timefn.c fileio.c fileio_asyncio.c $(RUST_STATICLIB) $(RUST_NOBENCH_CLI_STATICLIB)
|
||||
$(CC) $(FLAGS) -DZSTD_NOBENCH -DZSTD_NODICT -DZSTD_NOTRACE -UZSTD_LEGACY_SUPPORT -DZSTD_LEGACY_SUPPORT=0 $^ -o $@$(EXT)
|
||||
|
||||
CLEAN += zstd-decompress
|
||||
|
||||
@@ -10,73 +10,17 @@
|
||||
|
||||
/* The CLI parser and control flow live in rust/src/zstd_cli.rs. Keep this
|
||||
* translation unit as the stable C entry point used by program launchers. */
|
||||
#include <stddef.h> /* size_t */
|
||||
#define ZSTD_STATIC_LINKING_ONLY /* ZSTD_compressionParameters */
|
||||
#include "../lib/zstd.h"
|
||||
#ifndef ZSTD_NOBENCH
|
||||
# include "benchzstd.h" /* BMK_benchFilesAdvanced, BMK_syntheticTest */
|
||||
#endif
|
||||
|
||||
int ZSTD_rust_cli_main(int argCount, const char* const argv[]);
|
||||
const char* ZSTD_rust_cli_expected_version(void);
|
||||
int ZSTD_rust_cli_bench(const char* const* fileNames, unsigned nbFiles,
|
||||
const char* dictFileName,
|
||||
int startCLevel, int endCLevel,
|
||||
const ZSTD_compressionParameters* compressionParams,
|
||||
int displayLevel, unsigned nbSeconds,
|
||||
size_t blockSize, int nbWorkers, int mode);
|
||||
|
||||
const char* ZSTD_rust_cli_expected_version(void)
|
||||
{
|
||||
return ZSTD_VERSION_STRING;
|
||||
}
|
||||
|
||||
/* Benchmark bridge for the Rust CLI. Whether benchmarking exists is a C
|
||||
* preprocessor property (ZSTD_NOBENCH), so the decision stays in this shim:
|
||||
* the Rust frontend calls in unconditionally, and stripped program variants
|
||||
* never reference benchmark symbols.
|
||||
* @return the benchmark result code (>= 0), or -1 when unavailable. */
|
||||
int ZSTD_rust_cli_bench(const char* const* fileNames, unsigned nbFiles,
|
||||
const char* dictFileName,
|
||||
int startCLevel, int endCLevel,
|
||||
const ZSTD_compressionParameters* compressionParams,
|
||||
int displayLevel, unsigned nbSeconds,
|
||||
size_t blockSize, int nbWorkers, int mode)
|
||||
{
|
||||
#ifndef ZSTD_NOBENCH
|
||||
BMK_advancedParams_t advancedParams = BMK_initAdvancedParams();
|
||||
int startLevel = startCLevel;
|
||||
int endLevel = endCLevel;
|
||||
advancedParams.nbSeconds = nbSeconds;
|
||||
advancedParams.blockSize = blockSize;
|
||||
advancedParams.nbWorkers = nbWorkers;
|
||||
advancedParams.mode = (BMK_mode_t)mode;
|
||||
if (advancedParams.mode == BMK_decodeOnly) {
|
||||
startLevel = 0;
|
||||
endLevel = 0;
|
||||
}
|
||||
if (startLevel > ZSTD_maxCLevel()) startLevel = ZSTD_maxCLevel();
|
||||
if (endLevel > ZSTD_maxCLevel()) endLevel = ZSTD_maxCLevel();
|
||||
if (endLevel < startLevel) endLevel = startLevel;
|
||||
if (nbFiles == 0) {
|
||||
/* No input file: benchmark a synthetic sample (lorem generator). */
|
||||
return BMK_syntheticTest(-1.0, startLevel, endLevel,
|
||||
compressionParams, displayLevel,
|
||||
&advancedParams);
|
||||
}
|
||||
return BMK_benchFilesAdvanced(fileNames, nbFiles, dictFileName,
|
||||
startLevel, endLevel,
|
||||
compressionParams, displayLevel,
|
||||
&advancedParams);
|
||||
#else
|
||||
(void)fileNames; (void)nbFiles; (void)dictFileName;
|
||||
(void)startCLevel; (void)endCLevel; (void)compressionParams;
|
||||
(void)displayLevel; (void)nbSeconds; (void)blockSize; (void)nbWorkers;
|
||||
(void)mode;
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argCount, const char* argv[])
|
||||
{
|
||||
return ZSTD_rust_cli_main(argCount, argv);
|
||||
|
||||
+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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ RUST_CLI_HELPERS_STATICLIB := $(RUST_CLI_HELPERS_TARGET_DIR)/release/libzstd_cli
|
||||
RUST_CLI_HELPERS_STATICLIB_32 := $(RUST_CLI_HELPERS_TARGET_DIR)/$(RUST_TARGET_32)/release/libzstd_cli_rs.a
|
||||
RUST_CLI_HELPERS_CARGO_FLAGS := --manifest-path $(RUST_CLI_MANIFEST) --release \
|
||||
--target-dir $(RUST_CLI_HELPERS_TARGET_DIR) \
|
||||
--no-default-features
|
||||
--no-default-features --features helpers
|
||||
|
||||
$(RUST_CLI_HELPERS_STATICLIB): $(RUST_CLI_HELPER_SOURCES)
|
||||
$(CARGO) build $(RUST_CLI_HELPERS_CARGO_FLAGS)
|
||||
|
||||
Reference in New Issue
Block a user