Files
zstd-rs/rust/src/benchzstd.rs
T
ddidderr 96a0eabdb9 feat(bench): port benchzstd orchestration to Rust
Replace the program archive's C benchmark orchestration with the complete
Rust port while retaining the public benchzstd.h ABI and result/error
contracts. Include the module only in full CLI archives and remove only
benchzstd.c from the program source set, leaving C test targets on their
existing implementation.

Test Plan:
- cargo test --manifest-path /tmp/zstd-benchzstd.IHWpLD/Cargo.toml (6 passed)
- cargo check and cargo clippy --all-targets in the focused harness (passed)
- standalone release archive build and BMK_* symbol inspection (passed)
- make -C programs zstd (passed before concurrent CLI edits)
- make -C tests -B paramgrill (passed)
- native synthetic, multi-file, dictionary, and invalid-frame benchmark smoke tests (passed)
2026-07-18 01:47:33 +02:00

1797 lines
52 KiB
Rust

#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::too_many_arguments)]
//! Benchmark orchestration for the command-line programs.
//!
//! This is the Rust counterpart of `programs/benchzstd.c`. The public
//! structures intentionally mirror `programs/benchzstd.h` instead of using
//! Rust-only enums or references: they cross the C ABI by value or through
//! raw pointers. The actual codec calls use the public zstd context API, so
//! this module does not depend on the private `ZSTD_CCtx_s` or `ZSTD_DCtx_s`
//! layouts.
//!
//! The module is kept independent of the CLI dispatcher. The full CLI archive
//! includes it and the program build omits `benchzstd.c`; the existing
//! `benchfn.rs`, `datagen.rs`, and `lorem.rs` symbols are used through their
//! stable C ABI in that configuration.
use std::ffi::{c_char, c_void, CStr};
use std::fs;
use std::io::{self, Read, Write};
use std::mem::{offset_of, size_of};
use std::os::raw::{c_int, c_uint};
use std::path::PathBuf;
use std::ptr;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
pub const MB_UNIT: u64 = 1_000_000;
pub const BMK_TIMETEST_DEFAULT_S: c_uint = 3;
#[cfg(not(test))]
const BMK_RUNTEST_DEFAULT_MS: c_uint = 1_000;
#[cfg(test)]
const ZSTD_MAX_CLEVEL_FALLBACK: c_int = 22;
const ZSTD_CONTENTSIZE_UNKNOWN: u64 = u64::MAX;
const ZSTD_CONTENTSIZE_ERROR: u64 = u64::MAX - 1;
#[cfg(not(test))]
const ZSTD_RESET_SESSION_AND_PARAMETERS: c_int = 3;
const ZSTD_MAX_ERROR_CODE: c_int = 120;
#[cfg(test)]
const ZSTD_ERROR_GENERIC: c_int = 1;
const ZSTD_ERROR_DST_SIZE_TOO_SMALL: c_int = 70;
/* Public and experimental parameter values from zstd.h. */
const ZSTD_C_COMPRESSION_LEVEL: c_int = 100;
const ZSTD_C_WINDOW_LOG: c_int = 101;
const ZSTD_C_HASH_LOG: c_int = 102;
const ZSTD_C_CHAIN_LOG: c_int = 103;
const ZSTD_C_SEARCH_LOG: c_int = 104;
const ZSTD_C_MIN_MATCH: c_int = 105;
const ZSTD_C_TARGET_LENGTH: c_int = 106;
const ZSTD_C_STRATEGY: c_int = 107;
const ZSTD_C_TARGET_C_BLOCK_SIZE: c_int = 130;
const ZSTD_C_NB_WORKERS: c_int = 400;
const ZSTD_C_ENABLE_LDM: c_int = 160;
const ZSTD_C_LDM_HASH_LOG: c_int = 161;
const ZSTD_C_LDM_MIN_MATCH: c_int = 162;
const ZSTD_C_LDM_BUCKET_SIZE_LOG: c_int = 163;
const ZSTD_C_LDM_HASH_RATE_LOG: c_int = 164;
const ZSTD_C_LITERAL_COMPRESSION_MODE: c_int = 1002;
const ZSTD_C_USE_ROW_MATCH_FINDER: c_int = 1011;
/// `BMK_mode_t` is a C enum and therefore has the ABI of `int` here.
pub type BMK_mode_t = c_int;
pub const BMK_both: BMK_mode_t = 0;
pub const BMK_decodeOnly: BMK_mode_t = 1;
pub const BMK_compressOnly: BMK_mode_t = 2;
/// `ZSTD_ParamSwitch_e` is represented as an `int` in the C ABI.
pub type ZSTD_ParamSwitch_e = c_int;
pub const ZSTD_ps_auto: ZSTD_ParamSwitch_e = 0;
#[allow(dead_code)]
pub const ZSTD_ps_enable: ZSTD_ParamSwitch_e = 1;
#[allow(dead_code)]
pub const ZSTD_ps_disable: ZSTD_ParamSwitch_e = 2;
/// ABI-compatible `ZSTD_compressionParameters` from `zstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ZSTD_compressionParameters {
pub windowLog: u32,
pub chainLog: u32,
pub hashLog: u32,
pub searchLog: u32,
pub minMatch: u32,
pub targetLength: u32,
pub strategy: c_int,
}
/// ABI-compatible `BMK_benchResult_t` from `benchzstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BMK_benchResult_t {
pub cSize: usize,
pub cSpeed: u64,
pub dSpeed: u64,
pub cMem: usize,
}
/// Opaque result variant from `benchzstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct BMK_benchOutcome_t {
pub internal_never_ever_use_directly: BMK_benchResult_t,
pub tag: c_int,
}
/// ABI-compatible advanced benchmark controls.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BMK_advancedParams_t {
pub mode: BMK_mode_t,
pub nbSeconds: c_uint,
pub blockSize: usize,
pub targetCBlockSize: usize,
pub nbWorkers: c_int,
pub realTime: c_uint,
pub additionalParam: c_int,
pub ldmFlag: c_int,
pub ldmMinMatch: c_int,
pub ldmHashLog: c_int,
pub ldmBucketSizeLog: c_int,
pub ldmHashRateLog: c_int,
pub literalCompressionMode: ZSTD_ParamSwitch_e,
pub useRowMatchFinder: c_int,
}
impl Default for BMK_advancedParams_t {
fn default() -> Self {
Self {
mode: BMK_both,
nbSeconds: BMK_TIMETEST_DEFAULT_S,
blockSize: 0,
targetCBlockSize: 0,
nbWorkers: 0,
realTime: 0,
additionalParam: 0,
ldmFlag: 0,
ldmMinMatch: 0,
ldmHashLog: 0,
ldmBucketSizeLog: 0,
ldmHashRateLog: 0,
literalCompressionMode: ZSTD_ps_auto,
useRowMatchFinder: 0,
}
}
}
/* Keep the by-value C ABI honest on both 32- and 64-bit targets. */
const _: () = assert!(offset_of!(BMK_benchResult_t, cSpeed) == size_of::<usize>());
const _: () =
assert!(offset_of!(BMK_benchResult_t, dSpeed) == size_of::<usize>() + size_of::<u64>());
const _: () = assert!(offset_of!(BMK_benchOutcome_t, tag) == size_of::<BMK_benchResult_t>());
const _: () = assert!(size_of::<ZSTD_compressionParameters>() == 7 * size_of::<u32>());
const _: () = assert!(offset_of!(ZSTD_compressionParameters, strategy) == 6 * size_of::<u32>());
const _: () = assert!(offset_of!(BMK_advancedParams_t, nbSeconds) == size_of::<c_int>());
const _: () = assert!(offset_of!(BMK_advancedParams_t, blockSize) == 2 * size_of::<c_int>());
const _: () = assert!(
offset_of!(BMK_advancedParams_t, targetCBlockSize)
== 2 * size_of::<c_int>() + size_of::<usize>()
);
/* The following mirrors are private to this module and are used only to
* call the already migrated benchfn implementation by value. */
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct BMK_runTime_t {
nanoSecPerRun: f64,
sumOfReturn: usize,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
struct BMK_runOutcome_t {
internal_never_ever_use_directly: BMK_runTime_t,
error_result_never_ever_use_directly: usize,
error_tag_never_ever_use_directly: c_int,
}
type BMK_benchFn_t =
Option<unsafe extern "C" fn(*const c_void, usize, *mut c_void, usize, *mut c_void) -> usize>;
type BMK_initFn_t = Option<unsafe extern "C" fn(*mut c_void) -> usize>;
type BMK_errorFn_t = Option<unsafe extern "C" fn(usize) -> c_uint>;
#[repr(C)]
#[derive(Clone, Copy)]
struct BMK_benchParams_t {
benchFn: BMK_benchFn_t,
benchPayload: *mut c_void,
initFn: BMK_initFn_t,
initPayload: *mut c_void,
errorFn: BMK_errorFn_t,
blockCount: usize,
srcBuffers: *const *const c_void,
srcSizes: *const usize,
dstBuffers: *const *mut c_void,
dstCapacities: *const usize,
blockResults: *mut usize,
}
#[repr(C)]
struct BMK_timedFnState_t {
_private: [u8; 0],
}
#[repr(C)]
#[derive(Clone, Copy)]
struct ZSTD_inBuffer {
src: *const c_void,
size: usize,
pos: usize,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct ZSTD_outBuffer {
dst: *mut c_void,
size: usize,
pos: usize,
}
#[cfg(not(test))]
unsafe extern "C" {
fn ZSTD_maxCLevel() -> c_int;
fn ZSTD_compressBound(srcSize: usize) -> usize;
fn ZSTD_findDecompressedSize(src: *const c_void, srcSize: usize) -> u64;
fn ZSTD_createCCtx() -> *mut c_void;
fn ZSTD_freeCCtx(cctx: *mut c_void) -> usize;
fn ZSTD_CCtx_reset(cctx: *mut c_void, reset: c_int) -> usize;
fn ZSTD_CCtx_setParameter(cctx: *mut c_void, param: c_int, value: c_int) -> usize;
fn ZSTD_CCtx_loadDictionary(cctx: *mut c_void, dict: *const c_void, dictSize: usize) -> usize;
fn ZSTD_compress2(
cctx: *mut c_void,
dst: *mut c_void,
dstCapacity: usize,
src: *const c_void,
srcSize: usize,
) -> usize;
fn ZSTD_sizeof_CCtx(cctx: *const c_void) -> usize;
fn ZSTD_createDCtx() -> *mut c_void;
fn ZSTD_freeDCtx(dctx: *mut c_void) -> usize;
fn ZSTD_DCtx_reset(dctx: *mut c_void, reset: c_int) -> usize;
fn ZSTD_DCtx_loadDictionary(dctx: *mut c_void, dict: *const c_void, dictSize: usize) -> usize;
fn ZSTD_decompressStream(
dctx: *mut c_void,
output: *mut ZSTD_outBuffer,
input: *mut ZSTD_inBuffer,
) -> usize;
fn ZSTD_versionString() -> *const c_char;
fn BMK_createTimedFnState(total_ms: c_uint, run_ms: c_uint) -> *mut BMK_timedFnState_t;
fn BMK_freeTimedFnState(state: *mut BMK_timedFnState_t);
fn BMK_benchTimedFn(
state: *mut BMK_timedFnState_t,
params: BMK_benchParams_t,
) -> BMK_runOutcome_t;
fn BMK_isCompleted_TimedFn(state: *const BMK_timedFnState_t) -> c_int;
fn RDG_genBuffer(
buffer: *mut c_void,
size: usize,
matchProba: f64,
litProba: f64,
seed: c_uint,
);
fn LOREM_genBuffer(buffer: *mut c_void, size: usize, seed: c_uint);
}
#[inline]
fn error_value(code: c_int) -> usize {
(0usize).wrapping_sub(code as usize)
}
#[inline]
fn is_zstd_error(result: usize) -> bool {
result > error_value(ZSTD_MAX_ERROR_CODE)
}
#[cfg(not(test))]
unsafe fn zstd_max_c_level() -> c_int {
unsafe { ZSTD_maxCLevel() }
}
#[cfg(test)]
unsafe fn zstd_max_c_level() -> c_int {
ZSTD_MAX_CLEVEL_FALLBACK
}
#[cfg(not(test))]
unsafe fn zstd_compress_bound(size: usize) -> usize {
unsafe { ZSTD_compressBound(size) }
}
#[cfg(test)]
unsafe fn zstd_compress_bound(size: usize) -> usize {
size.saturating_add(64)
}
#[cfg(not(test))]
unsafe fn zstd_find_decompressed_size(src: *const c_void, size: usize) -> u64 {
unsafe { ZSTD_findDecompressedSize(src, size) }
}
#[cfg(test)]
unsafe fn zstd_find_decompressed_size(_src: *const c_void, _size: usize) -> u64 {
ZSTD_CONTENTSIZE_ERROR
}
#[cfg(not(test))]
unsafe fn zstd_create_cctx() -> *mut c_void {
unsafe { ZSTD_createCCtx() }
}
#[cfg(test)]
unsafe fn zstd_create_cctx() -> *mut c_void {
ptr::dangling_mut::<c_void>()
}
#[cfg(not(test))]
unsafe fn zstd_free_cctx(cctx: *mut c_void) {
unsafe { ZSTD_freeCCtx(cctx) };
}
#[cfg(test)]
unsafe fn zstd_free_cctx(_cctx: *mut c_void) {}
#[cfg(not(test))]
unsafe fn zstd_create_dctx() -> *mut c_void {
unsafe { ZSTD_createDCtx() }
}
#[cfg(test)]
unsafe fn zstd_create_dctx() -> *mut c_void {
ptr::dangling_mut::<c_void>()
}
#[cfg(not(test))]
unsafe fn zstd_free_dctx(dctx: *mut c_void) {
unsafe { ZSTD_freeDCtx(dctx) };
}
#[cfg(test)]
unsafe fn zstd_free_dctx(_dctx: *mut c_void) {}
#[cfg(not(test))]
unsafe fn zstd_cctx_reset(cctx: *mut c_void) -> usize {
unsafe { ZSTD_CCtx_reset(cctx, ZSTD_RESET_SESSION_AND_PARAMETERS) }
}
#[cfg(test)]
unsafe fn zstd_cctx_reset(_cctx: *mut c_void) -> usize {
0
}
#[cfg(not(test))]
unsafe fn zstd_cctx_set_parameter(cctx: *mut c_void, param: c_int, value: c_int) -> usize {
unsafe { ZSTD_CCtx_setParameter(cctx, param, value) }
}
#[cfg(test)]
unsafe fn zstd_cctx_set_parameter(_cctx: *mut c_void, _param: c_int, _value: c_int) -> usize {
0
}
#[cfg(not(test))]
unsafe fn zstd_cctx_load_dictionary(cctx: *mut c_void, dict: *const c_void, size: usize) -> usize {
unsafe { ZSTD_CCtx_loadDictionary(cctx, dict, size) }
}
#[cfg(test)]
unsafe fn zstd_cctx_load_dictionary(
_cctx: *mut c_void,
_dict: *const c_void,
_size: usize,
) -> usize {
0
}
#[cfg(not(test))]
unsafe fn zstd_compress2(
cctx: *mut c_void,
dst: *mut c_void,
dst_capacity: usize,
src: *const c_void,
src_size: usize,
) -> usize {
unsafe { ZSTD_compress2(cctx, dst, dst_capacity, src, src_size) }
}
#[cfg(test)]
unsafe fn zstd_compress2(
_cctx: *mut c_void,
_dst: *mut c_void,
_dst_capacity: usize,
_src: *const c_void,
_src_size: usize,
) -> usize {
error_value(ZSTD_ERROR_GENERIC)
}
#[cfg(not(test))]
unsafe fn zstd_sizeof_cctx(cctx: *const c_void) -> usize {
unsafe { ZSTD_sizeof_CCtx(cctx) }
}
#[cfg(test)]
unsafe fn zstd_sizeof_cctx(_cctx: *const c_void) -> usize {
0
}
#[cfg(not(test))]
unsafe fn zstd_dctx_reset(dctx: *mut c_void) -> usize {
unsafe { ZSTD_DCtx_reset(dctx, ZSTD_RESET_SESSION_AND_PARAMETERS) }
}
#[cfg(test)]
unsafe fn zstd_dctx_reset(_dctx: *mut c_void) -> usize {
0
}
#[cfg(not(test))]
unsafe fn zstd_dctx_load_dictionary(dctx: *mut c_void, dict: *const c_void, size: usize) -> usize {
unsafe { ZSTD_DCtx_loadDictionary(dctx, dict, size) }
}
#[cfg(test)]
unsafe fn zstd_dctx_load_dictionary(
_dctx: *mut c_void,
_dict: *const c_void,
_size: usize,
) -> usize {
0
}
#[cfg(not(test))]
unsafe fn zstd_decompress_stream(
dctx: *mut c_void,
output: *mut ZSTD_outBuffer,
input: *mut ZSTD_inBuffer,
) -> usize {
unsafe { ZSTD_decompressStream(dctx, output, input) }
}
#[cfg(test)]
unsafe fn zstd_decompress_stream(
_dctx: *mut c_void,
_output: *mut ZSTD_outBuffer,
_input: *mut ZSTD_inBuffer,
) -> usize {
error_value(ZSTD_ERROR_GENERIC)
}
#[cfg(not(test))]
unsafe fn zstd_version() -> String {
let ptr = unsafe { ZSTD_versionString() };
if ptr.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned()
}
}
#[cfg(test)]
unsafe fn zstd_version() -> String {
"test".to_owned()
}
#[cfg(not(test))]
unsafe fn timed_state_create(total_ms: c_uint) -> *mut BMK_timedFnState_t {
unsafe { BMK_createTimedFnState(total_ms, BMK_RUNTEST_DEFAULT_MS) }
}
#[cfg(test)]
unsafe fn timed_state_create(_total_ms: c_uint) -> *mut BMK_timedFnState_t {
ptr::null_mut()
}
#[cfg(not(test))]
unsafe fn timed_state_free(state: *mut BMK_timedFnState_t) {
unsafe { BMK_freeTimedFnState(state) };
}
#[cfg(test)]
unsafe fn timed_state_free(_state: *mut BMK_timedFnState_t) {}
#[cfg(not(test))]
unsafe fn timed_state_run(
state: *mut BMK_timedFnState_t,
params: BMK_benchParams_t,
) -> BMK_runOutcome_t {
unsafe { BMK_benchTimedFn(state, params) }
}
#[cfg(test)]
unsafe fn timed_state_run(
_state: *mut BMK_timedFnState_t,
_params: BMK_benchParams_t,
) -> BMK_runOutcome_t {
BMK_runOutcome_t {
internal_never_ever_use_directly: BMK_runTime_t::default(),
error_result_never_ever_use_directly: error_value(ZSTD_ERROR_GENERIC),
error_tag_never_ever_use_directly: 1,
}
}
#[cfg(not(test))]
unsafe fn timed_state_complete(state: *const BMK_timedFnState_t) -> bool {
unsafe { BMK_isCompleted_TimedFn(state) != 0 }
}
#[cfg(test)]
unsafe fn timed_state_complete(_state: *const BMK_timedFnState_t) -> bool {
true
}
#[cfg(not(test))]
unsafe fn generate_rdg(buffer: *mut c_void, size: usize, compressibility: f64) {
unsafe { RDG_genBuffer(buffer, size, compressibility, 0.0, 0) };
}
#[cfg(test)]
unsafe fn generate_rdg(buffer: *mut c_void, size: usize, _compressibility: f64) {
if size != 0 {
unsafe { ptr::write_bytes(buffer.cast::<u8>(), b'0', size) };
}
}
#[cfg(not(test))]
unsafe fn generate_lorem(buffer: *mut c_void, size: usize) {
unsafe { LOREM_genBuffer(buffer, size, 0) };
}
#[cfg(test)]
unsafe fn generate_lorem(buffer: *mut c_void, size: usize) {
if size != 0 {
unsafe { ptr::write_bytes(buffer.cast::<u8>(), b'l', size) };
}
}
#[inline]
fn run_succeeded(outcome: BMK_runOutcome_t) -> bool {
outcome.error_tag_never_ever_use_directly == 0
}
#[inline]
fn c_speed(src_size: usize, nano_sec_per_run: f64) -> u64 {
if nano_sec_per_run <= 0.0 || !nano_sec_per_run.is_finite() {
return 0;
}
((src_size as f64 * 1_000_000_000.0) / nano_sec_per_run) as u64
}
fn display_label(name: &str) -> String {
let name = display_name(name);
let bytes = name.as_bytes();
if bytes.len() <= 17 {
return name.to_owned();
}
String::from_utf8_lossy(&bytes[bytes.len() - 17..]).into_owned()
}
fn ratio_digits(ratio: f64) -> usize {
1 + usize::from(ratio < 100.0) + usize::from(ratio < 10.0)
}
fn output_progress(
display_level: c_int,
mark: &str,
name: &str,
src_size: usize,
c_size: usize,
ratio: f64,
c_speed: u64,
d_speed: Option<u64>,
) {
if display_level < 2 {
return;
}
let label = display_label(name);
let ratio = format!("{:.*}", ratio_digits(ratio), ratio);
let c_speed_digits = usize::from(c_speed < 10 * MB_UNIT);
let c_speed = format!("{:.*}", c_speed_digits + 1, c_speed as f64 / MB_UNIT as f64);
let mut stdout = io::stdout();
if let Some(d_speed) = d_speed {
let d_speed = format!("{d_speed:6.1}", d_speed = d_speed as f64 / MB_UNIT as f64);
let _ = write!(
stdout,
"{mark:>2}-{label:<17} :{src_size:>10} ->{c_size:>10} (x{ratio:>5}), {c_speed:>6} MB/s, {d_speed} MB/s\r"
);
} else {
let _ = write!(
stdout,
"{mark:>2}-{label:<17} :{src_size:>10} ->{c_size:>10} (x{ratio:>5}), {c_speed:>6} MB/s\r"
);
}
let _ = stdout.flush();
}
fn set_realtime_priority() {
#[cfg(unix)]
unsafe {
let _ = libc::setpriority(libc::PRIO_PROCESS, 0, -20);
}
}
#[inline]
fn try_bytes(size: usize) -> Result<Vec<u8>, ()> {
let mut result = Vec::new();
result.try_reserve_exact(size).map_err(|_| ())?;
result.resize(size, 0);
Ok(result)
}
#[inline]
fn try_vec<T>(capacity: usize) -> Result<Vec<T>, ()> {
let mut result = Vec::new();
result.try_reserve_exact(capacity).map_err(|_| ())?;
Ok(result)
}
unsafe fn c_string_path(value: *const c_char) -> Option<PathBuf> {
if value.is_null() {
return None;
}
let bytes = unsafe { CStr::from_ptr(value) }.to_bytes();
#[cfg(unix)]
{
Some(PathBuf::from(std::ffi::OsStr::from_bytes(bytes)))
}
#[cfg(not(unix))]
{
Some(PathBuf::from(String::from_utf8_lossy(bytes).into_owned()))
}
}
fn display_name(value: &str) -> &str {
value.rsplit(['/', '\\']).next().unwrap_or(value)
}
fn zstd_error_callback(result: usize) -> c_uint {
c_uint::from(is_zstd_error(result))
}
unsafe extern "C" fn zstd_error_callback_abi(result: usize) -> c_uint {
zstd_error_callback(result)
}
#[derive(Clone, Copy)]
struct BenchCodecContext {
cctx: *mut c_void,
dctx: *mut c_void,
dict: *const c_void,
dict_size: usize,
c_level: c_int,
cparams: ZSTD_compressionParameters,
adv: BMK_advancedParams_t,
init_error: usize,
}
unsafe fn set_c_parameter(context: &mut BenchCodecContext, param: c_int, value: c_int) -> bool {
let result = unsafe { zstd_cctx_set_parameter(context.cctx, param, value) };
if is_zstd_error(result) {
context.init_error = result;
false
} else {
true
}
}
unsafe fn initialize_compression(context: &mut BenchCodecContext) -> usize {
context.init_error = 0;
let reset = unsafe { zstd_cctx_reset(context.cctx) };
if is_zstd_error(reset) {
context.init_error = reset;
return reset;
}
let workers = if context.adv.nbWorkers == 1 {
0
} else {
context.adv.nbWorkers
};
let parameters = [
(ZSTD_C_NB_WORKERS, workers),
(ZSTD_C_COMPRESSION_LEVEL, context.c_level),
(ZSTD_C_USE_ROW_MATCH_FINDER, context.adv.useRowMatchFinder),
(ZSTD_C_ENABLE_LDM, context.adv.ldmFlag),
(ZSTD_C_LDM_MIN_MATCH, context.adv.ldmMinMatch),
(ZSTD_C_LDM_HASH_LOG, context.adv.ldmHashLog),
(ZSTD_C_LDM_BUCKET_SIZE_LOG, context.adv.ldmBucketSizeLog),
(ZSTD_C_LDM_HASH_RATE_LOG, context.adv.ldmHashRateLog),
(ZSTD_C_WINDOW_LOG, context.cparams.windowLog as c_int),
(ZSTD_C_HASH_LOG, context.cparams.hashLog as c_int),
(ZSTD_C_CHAIN_LOG, context.cparams.chainLog as c_int),
(ZSTD_C_SEARCH_LOG, context.cparams.searchLog as c_int),
(ZSTD_C_MIN_MATCH, context.cparams.minMatch as c_int),
(ZSTD_C_TARGET_LENGTH, context.cparams.targetLength as c_int),
(
ZSTD_C_LITERAL_COMPRESSION_MODE,
context.adv.literalCompressionMode,
),
(ZSTD_C_STRATEGY, context.cparams.strategy),
(
ZSTD_C_TARGET_C_BLOCK_SIZE,
context.adv.targetCBlockSize as c_int,
),
];
for (param, value) in parameters {
if !unsafe { set_c_parameter(context, param, value) } {
return context.init_error;
}
}
let result =
unsafe { zstd_cctx_load_dictionary(context.cctx, context.dict, context.dict_size) };
if is_zstd_error(result) {
context.init_error = result;
}
result
}
unsafe fn initialize_decompression(context: &mut BenchCodecContext) -> usize {
context.init_error = 0;
let reset = unsafe { zstd_dctx_reset(context.dctx) };
if is_zstd_error(reset) {
context.init_error = reset;
return reset;
}
let result =
unsafe { zstd_dctx_load_dictionary(context.dctx, context.dict, context.dict_size) };
if is_zstd_error(result) {
context.init_error = result;
}
result
}
unsafe extern "C" fn initialize_compression_abi(payload: *mut c_void) -> usize {
let context = unsafe { &mut *payload.cast::<BenchCodecContext>() };
unsafe { initialize_compression(context) }
}
unsafe extern "C" fn initialize_decompression_abi(payload: *mut c_void) -> usize {
let context = unsafe { &mut *payload.cast::<BenchCodecContext>() };
unsafe { initialize_decompression(context) }
}
unsafe extern "C" fn compress_block_abi(
src: *const c_void,
src_size: usize,
dst: *mut c_void,
dst_capacity: usize,
payload: *mut c_void,
) -> usize {
let context = unsafe { &mut *payload.cast::<BenchCodecContext>() };
if context.init_error != 0 {
return context.init_error;
}
unsafe { zstd_compress2(context.cctx, dst, dst_capacity, src, src_size) }
}
unsafe extern "C" fn decompress_block_abi(
src: *const c_void,
src_size: usize,
dst: *mut c_void,
dst_capacity: usize,
payload: *mut c_void,
) -> usize {
let context = unsafe { &mut *payload.cast::<BenchCodecContext>() };
if context.init_error != 0 {
return context.init_error;
}
let mut input = ZSTD_inBuffer {
src,
size: src_size,
pos: 0,
};
let mut output = ZSTD_outBuffer {
dst,
size: dst_capacity,
pos: 0,
};
let mut remaining = 1usize;
while remaining != 0 {
if output.pos == output.size {
return error_value(ZSTD_ERROR_DST_SIZE_TOO_SMALL);
}
remaining = unsafe { zstd_decompress_stream(context.dctx, &mut output, &mut input) };
if is_zstd_error(remaining) {
return remaining;
}
}
output.pos
}
struct BlockSpec {
src_offset: usize,
src_size: usize,
result_size: usize,
}
struct BenchBuffers {
src_ptrs: Vec<*const c_void>,
src_sizes: Vec<usize>,
c_ptrs: Vec<*mut c_void>,
c_capacities: Vec<usize>,
c_sizes: Vec<usize>,
result_ptrs: Vec<*mut c_void>,
result_capacities: Vec<usize>,
_compressed_storage: Option<Vec<u8>>,
result_storage: Vec<u8>,
}
unsafe fn build_block_specs(
src: *const c_void,
src_size: usize,
file_sizes: &[usize],
mode: BMK_mode_t,
block_size: usize,
) -> Result<(Vec<BlockSpec>, usize), c_int> {
let mut specs = try_vec(file_sizes.len()).map_err(|_| 31)?;
let mut source_offset = 0usize;
let mut logical_size = src_size;
if mode == BMK_decodeOnly {
logical_size = 0;
for &file_size in file_sizes {
if source_offset.checked_add(file_size).is_none() {
return Err(32);
}
let file_src = unsafe { (src.cast::<u8>()).add(source_offset) }.cast();
let decoded = unsafe { zstd_find_decompressed_size(file_src, file_size) };
if decoded == ZSTD_CONTENTSIZE_UNKNOWN || decoded == ZSTD_CONTENTSIZE_ERROR {
return Err(32);
}
let decoded = usize::try_from(decoded).map_err(|_| 32)?;
logical_size = logical_size.checked_add(decoded).ok_or(32)?;
specs.push(BlockSpec {
src_offset: source_offset,
src_size: file_size,
result_size: decoded,
});
source_offset += file_size;
}
return Ok((specs, logical_size));
}
for &file_size in file_sizes {
let mut remaining = file_size;
while remaining != 0 {
let this_size = remaining.min(block_size);
specs.push(BlockSpec {
src_offset: source_offset,
src_size: this_size,
result_size: this_size,
});
source_offset += this_size;
remaining -= this_size;
}
}
Ok((specs, logical_size))
}
unsafe fn build_bench_buffers(
src: *const c_void,
src_size: usize,
dst: *mut c_void,
dst_capacity: usize,
specs: &[BlockSpec],
mode: BMK_mode_t,
) -> Result<BenchBuffers, c_int> {
let mut compressed_size = 0usize;
let mut compressed_capacities = try_vec(specs.len()).map_err(|_| 31)?;
for spec in specs {
let capacity = if mode == BMK_decodeOnly {
spec.src_size
} else {
let bound = unsafe { zstd_compress_bound(spec.src_size) };
if is_zstd_error(bound) {
return Err(31);
}
bound
};
compressed_size = compressed_size.checked_add(capacity).ok_or(31)?;
compressed_capacities.push(capacity);
}
if (dst.is_null()) != (dst_capacity == 0) {
return Err(32);
}
if !dst.is_null() && dst_capacity < compressed_size {
return Err(32);
}
let compressed_storage = if dst.is_null() {
let mut capacity = compressed_size;
if mode != BMK_decodeOnly {
capacity = capacity
.checked_add(specs.len().saturating_mul(1024))
.ok_or(31)?;
}
Some(try_bytes(capacity).map_err(|_| 31)?)
} else {
None
};
let compressed_base = if let Some(storage) = compressed_storage.as_ref() {
storage.as_ptr().cast_mut()
} else {
dst.cast::<u8>()
};
if mode == BMK_decodeOnly && src_size != 0 {
unsafe { ptr::copy_nonoverlapping(src.cast::<u8>(), compressed_base, src_size) };
}
let mut result_size = 0usize;
for spec in specs {
result_size = result_size.checked_add(spec.result_size).ok_or(31)?;
}
let result_storage = try_bytes(result_size).map_err(|_| 31)?;
let result_base = result_storage.as_ptr().cast_mut();
let mut src_ptrs = try_vec(specs.len()).map_err(|_| 31)?;
let mut src_sizes = try_vec(specs.len()).map_err(|_| 31)?;
let mut c_ptrs = try_vec(specs.len()).map_err(|_| 31)?;
let mut c_capacities = try_vec(specs.len()).map_err(|_| 31)?;
let mut c_sizes = try_vec(specs.len()).map_err(|_| 31)?;
let mut result_ptrs = try_vec(specs.len()).map_err(|_| 31)?;
let mut result_capacities = try_vec(specs.len()).map_err(|_| 31)?;
let mut c_offset = 0usize;
let mut result_offset = 0usize;
for (index, spec) in specs.iter().enumerate() {
let src_ptr = unsafe { src.cast::<u8>().add(spec.src_offset) }.cast();
let c_ptr = unsafe { compressed_base.add(c_offset) }.cast();
let result_ptr = unsafe { result_base.add(result_offset) }.cast();
src_ptrs.push(src_ptr);
src_sizes.push(spec.src_size);
c_ptrs.push(c_ptr);
c_capacities.push(compressed_capacities[index]);
c_sizes.push(if mode == BMK_decodeOnly {
spec.src_size
} else {
0
});
result_ptrs.push(result_ptr);
result_capacities.push(spec.result_size);
c_offset += compressed_capacities[index];
result_offset += spec.result_size;
}
Ok(BenchBuffers {
src_ptrs,
src_sizes,
c_ptrs,
c_capacities,
c_sizes,
result_ptrs,
result_capacities,
_compressed_storage: compressed_storage,
result_storage,
})
}
unsafe fn bench_mem_advanced(
src: *const c_void,
src_size: usize,
dst: *mut c_void,
dst_capacity: usize,
file_sizes: &[usize],
c_level: c_int,
cparams: ZSTD_compressionParameters,
dict: *const c_void,
dict_size: usize,
display_level: c_int,
name: &str,
adv: BMK_advancedParams_t,
) -> BMK_benchOutcome_t {
if (dst.is_null()) != (dst_capacity == 0) {
return bench_error(32);
}
if file_sizes.is_empty() || src.is_null() && src_size != 0 {
return bench_error(32);
}
let file_total = file_sizes
.iter()
.try_fold(0usize, |total, size| total.checked_add(*size));
if file_total != Some(src_size) {
return bench_error(32);
}
if dict_size != 0 && dict.is_null() {
return bench_error(32);
}
if src_size == 0 {
return bench_error(31);
}
let block_size = if adv.mode != BMK_decodeOnly && adv.blockSize >= 32 {
adv.blockSize
} else {
src_size
};
let (specs, logical_size) = match unsafe {
build_block_specs(src, src_size, file_sizes, adv.mode, block_size.max(1))
} {
Ok(value) => value,
Err(code) => return bench_error(code),
};
if specs.is_empty() {
return bench_error(31);
}
let mut buffers =
match unsafe { build_bench_buffers(src, src_size, dst, dst_capacity, &specs, adv.mode) } {
Ok(value) => value,
Err(code) => return bench_error(code),
};
let cctx = unsafe { zstd_create_cctx() };
let dctx = unsafe { zstd_create_dctx() };
if cctx.is_null() || dctx.is_null() {
unsafe {
zstd_free_cctx(cctx);
zstd_free_dctx(dctx);
}
return bench_error(31);
}
let total_ms = adv.nbSeconds.wrapping_mul(1_000);
let compression_state = unsafe { timed_state_create(total_ms) };
let decompression_state = unsafe { timed_state_create(total_ms) };
if compression_state.is_null() || decompression_state.is_null() {
unsafe {
timed_state_free(compression_state);
timed_state_free(decompression_state);
zstd_free_cctx(cctx);
zstd_free_dctx(dctx);
}
return bench_error(31);
}
let mut context = BenchCodecContext {
cctx,
dctx,
dict,
dict_size,
c_level,
cparams,
adv,
init_error: 0,
};
let payload = (&mut context as *mut BenchCodecContext).cast::<c_void>();
let mut compression_params = BMK_benchParams_t {
benchFn: Some(compress_block_abi),
benchPayload: payload,
initFn: Some(initialize_compression_abi),
initPayload: payload,
errorFn: Some(zstd_error_callback_abi),
blockCount: buffers.src_ptrs.len(),
srcBuffers: buffers.src_ptrs.as_ptr(),
srcSizes: buffers.src_sizes.as_ptr(),
dstBuffers: buffers.c_ptrs.as_ptr(),
dstCapacities: buffers.c_capacities.as_ptr(),
blockResults: buffers.c_sizes.as_mut_ptr(),
};
let decompression_params = BMK_benchParams_t {
benchFn: Some(decompress_block_abi),
benchPayload: payload,
initFn: Some(initialize_decompression_abi),
initPayload: payload,
errorFn: Some(zstd_error_callback_abi),
blockCount: buffers.c_ptrs.len(),
srcBuffers: buffers.c_ptrs.as_ptr().cast(),
srcSizes: buffers.c_sizes.as_ptr(),
dstBuffers: buffers.result_ptrs.as_ptr(),
dstCapacities: buffers.result_capacities.as_ptr(),
blockResults: ptr::null_mut(),
};
let mut compression_done = adv.mode == BMK_decodeOnly;
let mut decompression_done = adv.mode == BMK_compressOnly;
let mut result = BMK_benchResult_t::default();
let mut c_size = if adv.mode == BMK_decodeOnly {
src_size
} else {
0
};
let mut ratio = if c_size == 0 {
0.0
} else {
logical_size as f64 / c_size as f64
};
let marks = [" |", " /", " =", " \\"];
let mut mark_number = 0usize;
if display_level >= 2 {
let mut stdout = io::stdout();
let _ = write!(stdout, "\r{:>70}\r", "");
let _ = stdout.flush();
output_progress(
display_level,
marks[mark_number],
name,
logical_size,
c_size,
ratio,
result.cSpeed,
None,
);
}
while !(compression_done && decompression_done) {
if !compression_done {
let outcome = unsafe { timed_state_run(compression_state, compression_params) };
if !run_succeeded(outcome) {
unsafe {
timed_state_free(compression_state);
timed_state_free(decompression_state);
zstd_free_cctx(cctx);
zstd_free_dctx(dctx);
}
return bench_error(30);
}
let runtime = outcome.internal_never_ever_use_directly;
c_size = runtime.sumOfReturn;
ratio = if c_size == 0 {
0.0
} else {
logical_size as f64 / c_size as f64
};
result.cSize = c_size;
result.cSpeed = result
.cSpeed
.max(c_speed(logical_size, runtime.nanoSecPerRun));
output_progress(
display_level,
marks[mark_number],
name,
logical_size,
c_size,
ratio,
result.cSpeed,
None,
);
compression_done = unsafe { timed_state_complete(compression_state) };
}
if !decompression_done {
let outcome = unsafe { timed_state_run(decompression_state, decompression_params) };
if !run_succeeded(outcome) {
unsafe {
timed_state_free(compression_state);
timed_state_free(decompression_state);
zstd_free_cctx(cctx);
zstd_free_dctx(dctx);
}
return bench_error(30);
}
let runtime = outcome.internal_never_ever_use_directly;
result.dSpeed = result
.dSpeed
.max(c_speed(logical_size, runtime.nanoSecPerRun));
output_progress(
display_level,
marks[mark_number],
name,
logical_size,
c_size,
ratio,
result.cSpeed,
Some(result.dSpeed),
);
decompression_done = unsafe { timed_state_complete(decompression_state) };
}
mark_number = (mark_number + 1) % marks.len();
}
if adv.mode == BMK_both {
let result_bytes = &buffers.result_storage;
let source_bytes = unsafe { std::slice::from_raw_parts(src.cast::<u8>(), src_size) };
if result_bytes != source_bytes && display_level >= 1 {
eprintln!(
"!!! WARNING !!! {:>17} : Invalid Checksum",
display_name(name)
);
}
}
if display_level == 1 {
let c_speed = result.cSpeed as f64 / MB_UNIT as f64;
let d_speed = result.dSpeed as f64 / MB_UNIT as f64;
if adv.additionalParam != 0 {
println!(
"-{c_level:<3}{c_size:>11} ({ratio:5.3}) {c_speed:6.2} MB/s {d_speed:6.1} MB/s {} (param={})",
display_name(name), adv.additionalParam
);
} else {
println!(
"-{c_level:<3}{c_size:>11} ({ratio:5.3}) {c_speed:6.2} MB/s {d_speed:6.1} MB/s {}",
display_name(name)
);
}
} else if display_level >= 2 {
println!("{c_level:2}#");
}
let window_memory = if cparams.windowLog < usize::BITS {
1usize << cparams.windowLog
} else {
0
};
result.cMem = window_memory.saturating_add(unsafe { zstd_sizeof_cctx(cctx) });
unsafe {
timed_state_free(compression_state);
timed_state_free(decompression_state);
zstd_free_cctx(cctx);
zstd_free_dctx(dctx);
}
/* Keep buffers alive until every callback and context has finished. */
let _ = (&mut buffers, &mut compression_params, ratio);
result_outcome(result)
}
fn bench_error(_tag: c_int) -> BMK_benchOutcome_t {
BMK_benchOutcome_t {
internal_never_ever_use_directly: BMK_benchResult_t::default(),
tag: 1,
}
}
fn result_outcome(result: BMK_benchResult_t) -> BMK_benchOutcome_t {
BMK_benchOutcome_t {
internal_never_ever_use_directly: result,
tag: 0,
}
}
unsafe fn bench_c_levels(
src: *const c_void,
src_size: usize,
file_sizes: &[usize],
start_level: c_int,
end_level: c_int,
cparams: ZSTD_compressionParameters,
dict: *const c_void,
dict_size: usize,
display_level: c_int,
name: &str,
adv: BMK_advancedParams_t,
) -> c_int {
if end_level > unsafe { zstd_max_c_level() } || end_level < start_level {
if display_level >= 1 {
eprintln!(
"{}",
if end_level < start_level {
"Invalid Compression Level Range"
} else {
"Invalid Compression Level"
}
);
}
return 15;
}
if adv.realTime != 0 {
if display_level >= 2 {
eprintln!("Note : switching to real-time priority");
}
set_realtime_priority();
}
if display_level == 1 && adv.additionalParam == 0 {
let version = unsafe { zstd_version() };
println!(
"bench {version} : input {} bytes, {} seconds, {} KB blocks",
src_size,
adv.nbSeconds,
adv.blockSize >> 10
);
}
let mut level = start_level;
loop {
let outcome = unsafe {
bench_mem_advanced(
src,
src_size,
ptr::null_mut(),
0,
file_sizes,
level,
cparams,
dict,
dict_size,
display_level,
name,
adv,
)
};
if BMK_isSuccessful_benchOutcome(outcome) == 0 {
return 1;
}
if level == end_level {
break;
}
level = level.saturating_add(1);
}
0
}
fn max_memory() -> u64 {
if usize::BITS == 32 {
(2u64 << 30) - (64u64 << 20)
} else {
1u64 << (usize::BITS - 31)
}
}
fn find_max_memory(required_memory: u64) -> usize {
let step = 64u64 << 20;
let mut required = ((required_memory >> 26) + 1) << 26;
required = required.saturating_add(step).min(max_memory());
loop {
let allocation = usize::try_from(required)
.ok()
.map(|size| unsafe { libc::malloc(size) });
required = required.saturating_sub(step);
if let Some(allocation) = allocation {
if !allocation.is_null() {
unsafe { libc::free(allocation) };
return required as usize;
}
}
if required == 0 {
return 0;
}
}
}
fn load_files(
buffer: &mut [u8],
file_sizes: &mut [usize],
paths: &[PathBuf],
display_level: c_int,
) -> Result<(), c_int> {
let mut position = 0usize;
let mut total_size = 0usize;
for (index, path) in paths.iter().enumerate() {
let metadata = match fs::metadata(path) {
Ok(metadata) => metadata,
Err(_) => return Err(1),
};
if metadata.is_dir() {
if display_level >= 2 {
println!("Ignoring {} directory... ", path.display());
}
file_sizes[index] = 0;
continue;
}
if !metadata.is_file() {
file_sizes[index] = 0;
continue;
}
let mut file_size = usize::try_from(metadata.len()).map_err(|_| 1)?;
let available = buffer.len().saturating_sub(position);
let truncated = file_size > available;
if truncated {
file_size = available;
}
if display_level >= 2 {
print!("Loading {}... \r", path.display());
let _ = io::stdout().flush();
}
let mut file = fs::File::open(path).map_err(|_| 1)?;
file.read_exact(&mut buffer[position..position + file_size])
.map_err(|_| 1)?;
file_sizes[index] = file_size;
position += file_size;
total_size += file_size;
if truncated {
break;
}
}
if total_size == 0 {
return Err(12);
}
Ok(())
}
unsafe fn collect_file_inputs(
file_names: *const *const c_char,
nb_files: c_uint,
display_level: c_int,
) -> Result<(Vec<u8>, Vec<usize>, String), c_int> {
if file_names.is_null() || nb_files == 0 {
return Err(13);
}
let mut paths = try_vec(nb_files as usize).map_err(|_| 16)?;
for index in 0..nb_files as usize {
let path = unsafe { c_string_path(*file_names.add(index)) }.ok_or(15)?;
paths.push(path);
}
let mut total_size = 0u64;
let mut sizes = try_vec(paths.len()).map_err(|_| 16)?;
for path in &paths {
let metadata = fs::metadata(path).map_err(|_| 15)?;
if !metadata.is_file() {
return Err(15);
}
total_size = total_size.checked_add(metadata.len()).ok_or(15)?;
sizes.push(usize::try_from(metadata.len()).map_err(|_| 15)?);
}
if total_size == 0 {
return Err(1);
}
let benched_size = (find_max_memory(total_size.saturating_mul(3)) / 3)
.min(usize::try_from(total_size).map_err(|_| 20)?);
if benched_size == 0 {
return Err(20);
}
if u64::try_from(benched_size).unwrap_or(u64::MAX) < total_size {
eprintln!(
"Not enough memory; testing {} MB only...",
benched_size >> 20
);
}
let mut source = try_bytes(benched_size).map_err(|_| 20)?;
sizes.fill(0);
match load_files(&mut source, &mut sizes, &paths, display_level) {
Ok(()) => {}
Err(12) => return Err(1),
Err(_) => return Err(1),
}
let name = if nb_files > 1 {
format!(" {nb_files} files")
} else {
unsafe { CStr::from_ptr(*file_names) }
.to_string_lossy()
.into_owned()
};
Ok((source, sizes, name))
}
unsafe fn collect_dictionary(
dict_file_name: *const c_char,
display_level: c_int,
) -> Result<Vec<u8>, c_int> {
let Some(path) = (unsafe { c_string_path(dict_file_name) }) else {
return Ok(Vec::new());
};
let metadata = fs::metadata(&path).map_err(|_| 17)?;
if !metadata.is_file() {
return Err(17);
}
let size = usize::try_from(metadata.len()).map_err(|_| 18)?;
if size > 64 * 1024 * 1024 {
return Err(18);
}
let mut data = try_bytes(size).map_err(|_| 19)?;
let mut file_sizes = [0usize; 1];
match load_files(
data.as_mut_slice(),
&mut file_sizes,
std::slice::from_ref(&path),
display_level,
) {
Ok(()) => {}
Err(_) => return Err(1),
}
Ok(data)
}
/// Returns the defaults used by the non-advanced benchmark entry points.
#[no_mangle]
pub extern "C" fn BMK_initAdvancedParams() -> BMK_advancedParams_t {
BMK_advancedParams_t::default()
}
/// Returns nonzero when a benchmark outcome contains a valid result.
#[no_mangle]
pub extern "C" fn BMK_isSuccessful_benchOutcome(outcome: BMK_benchOutcome_t) -> c_int {
c_int::from(outcome.tag == 0)
}
/// Extracts a valid result and aborts when called on an error outcome.
#[no_mangle]
pub extern "C" fn BMK_extract_benchResult(outcome: BMK_benchOutcome_t) -> BMK_benchResult_t {
if outcome.tag != 0 {
std::process::abort();
}
outcome.internal_never_ever_use_directly
}
/// Core memory-to-memory benchmark entry point from `benchzstd.h`.
#[no_mangle]
pub unsafe extern "C" fn BMK_benchMemAdvanced(
srcBuffer: *const c_void,
srcSize: usize,
dstBuffer: *mut c_void,
dstCapacity: usize,
fileSizes: *const usize,
nbFiles: c_uint,
cLevel: c_int,
comprParams: *const ZSTD_compressionParameters,
dictBuffer: *const c_void,
dictBufferSize: usize,
displayLevel: c_int,
displayName: *const c_char,
adv: *const BMK_advancedParams_t,
) -> BMK_benchOutcome_t {
if fileSizes.is_null() || comprParams.is_null() || adv.is_null() {
return bench_error(32);
}
let file_sizes = unsafe { std::slice::from_raw_parts(fileSizes, nbFiles as usize) };
let name = if displayName.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(displayName) }
.to_string_lossy()
.into_owned()
};
unsafe {
bench_mem_advanced(
srcBuffer,
srcSize,
dstBuffer,
dstCapacity,
file_sizes,
cLevel,
*comprParams,
dictBuffer,
dictBufferSize,
displayLevel,
&name,
*adv,
)
}
}
/// Default-parameter memory-to-memory benchmark entry point.
#[no_mangle]
pub unsafe extern "C" fn BMK_benchMem(
srcBuffer: *const c_void,
srcSize: usize,
fileSizes: *const usize,
nbFiles: c_uint,
cLevel: c_int,
comprParams: *const ZSTD_compressionParameters,
dictBuffer: *const c_void,
dictBufferSize: usize,
displayLevel: c_int,
displayName: *const c_char,
) -> BMK_benchOutcome_t {
let adv = BMK_initAdvancedParams();
unsafe {
BMK_benchMemAdvanced(
srcBuffer,
srcSize,
ptr::null_mut(),
0,
fileSizes,
nbFiles,
cLevel,
comprParams,
dictBuffer,
dictBufferSize,
displayLevel,
displayName,
&adv,
)
}
}
/// Loads files, optionally loads a dictionary, and benchmarks every level in
/// the requested range.
#[no_mangle]
pub unsafe extern "C" fn BMK_benchFilesAdvanced(
fileNamesTable: *const *const c_char,
nbFiles: c_uint,
dictFileName: *const c_char,
startCLevel: c_int,
endCLevel: c_int,
compressionParams: *const ZSTD_compressionParameters,
displayLevel: c_int,
adv: *const BMK_advancedParams_t,
) -> c_int {
if nbFiles == 0 {
return 13;
}
if compressionParams.is_null() || adv.is_null() {
return 15;
}
if endCLevel > unsafe { zstd_max_c_level() } {
return 14;
}
let (source, file_sizes, name) =
match unsafe { collect_file_inputs(fileNamesTable, nbFiles, displayLevel) } {
Ok(value) => value,
Err(code) => return code,
};
let dictionary = match unsafe { collect_dictionary(dictFileName, displayLevel) } {
Ok(value) => value,
Err(code) => return code,
};
let dict_ptr = if dictionary.is_empty() {
ptr::null()
} else {
dictionary.as_ptr().cast()
};
unsafe {
bench_c_levels(
source.as_ptr().cast(),
source.len(),
&file_sizes,
startCLevel,
endCLevel,
*compressionParams,
dict_ptr,
dictionary.len(),
displayLevel,
&name,
*adv,
)
}
}
/// Single-level convenience wrapper used by older callers.
#[no_mangle]
pub unsafe extern "C" fn BMK_benchFiles(
fileNamesTable: *const *const c_char,
nbFiles: c_uint,
dictFileName: *const c_char,
cLevel: c_int,
compressionParams: *const ZSTD_compressionParameters,
displayLevel: c_int,
) -> c_int {
let adv = BMK_initAdvancedParams();
unsafe {
BMK_benchFilesAdvanced(
fileNamesTable,
nbFiles,
dictFileName,
cLevel,
cLevel,
compressionParams,
displayLevel,
&adv,
)
}
}
/// Generates a deterministic synthetic sample and benchmarks the requested
/// level range. Negative compressibility selects the lorem ipsum generator,
/// matching `programs/benchzstd.c`.
#[no_mangle]
pub unsafe extern "C" fn BMK_syntheticTest(
compressibility: f64,
startingCLevel: c_int,
endCLevel: c_int,
compressionParams: *const ZSTD_compressionParameters,
displayLevel: c_int,
adv: *const BMK_advancedParams_t,
) -> c_int {
if compressionParams.is_null() || adv.is_null() {
return 15;
}
let adv_value = unsafe { *adv };
let size = if adv_value.blockSize == 0 {
10_000_000
} else {
adv_value.blockSize
};
let mut source = match try_bytes(size) {
Ok(value) => value,
Err(()) => return 16,
};
let name = if compressibility < 0.0 {
unsafe { generate_lorem(source.as_mut_ptr().cast(), source.len()) };
"Lorem ipsum".to_owned()
} else {
unsafe { generate_rdg(source.as_mut_ptr().cast(), source.len(), compressibility) };
format!("Synthetic {}%", (compressibility * 100.0) as u32)
};
let file_sizes = [source.len()];
unsafe {
bench_c_levels(
source.as_ptr().cast(),
source.len(),
&file_sizes,
startingCLevel,
endCLevel,
*compressionParams,
ptr::null(),
0,
displayLevel,
&name,
adv_value,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn advanced_defaults_match_benchzstd_c() {
let defaults = BMK_initAdvancedParams();
assert_eq!(defaults.mode, BMK_both);
assert_eq!(defaults.nbSeconds, BMK_TIMETEST_DEFAULT_S);
assert_eq!(defaults.blockSize, 0);
assert_eq!(defaults.targetCBlockSize, 0);
assert_eq!(defaults.nbWorkers, 0);
assert_eq!(defaults.realTime, 0);
assert_eq!(defaults.additionalParam, 0);
assert_eq!(defaults.ldmFlag, 0);
assert_eq!(defaults.ldmMinMatch, 0);
assert_eq!(defaults.ldmHashLog, 0);
assert_eq!(defaults.ldmBucketSizeLog, 0);
assert_eq!(defaults.ldmHashRateLog, 0);
assert_eq!(defaults.literalCompressionMode, ZSTD_ps_auto);
assert_eq!(defaults.useRowMatchFinder, 0);
}
#[test]
fn outcome_accessors_extract_only_valid_results() {
let result = BMK_benchResult_t {
cSize: 123,
cSpeed: 456,
dSpeed: 789,
cMem: 321,
};
let valid = result_outcome(result);
assert_eq!(BMK_isSuccessful_benchOutcome(valid), 1);
assert_eq!(BMK_extract_benchResult(valid), result);
let invalid = bench_error(30);
assert_eq!(BMK_isSuccessful_benchOutcome(invalid), 0);
assert_eq!(invalid.tag, 1);
}
#[test]
fn outcome_layout_matches_header_variant() {
assert_eq!(size_of::<BMK_benchResult_t>(), size_of::<usize>() * 4);
assert_eq!(
offset_of!(BMK_benchOutcome_t, tag),
size_of::<BMK_benchResult_t>()
);
}
#[test]
fn public_parameter_layout_matches_the_c_headers() {
assert_eq!(
offset_of!(ZSTD_compressionParameters, strategy),
6 * size_of::<u32>()
);
assert_eq!(
offset_of!(BMK_advancedParams_t, targetCBlockSize),
2 * size_of::<c_int>() + size_of::<usize>()
);
}
#[test]
fn block_partitioning_preserves_file_boundaries() {
let (specs, logical_size) = unsafe {
build_block_specs(ptr::null(), 10, &[3, 7], BMK_both, 4).expect("valid block partition")
};
assert_eq!(logical_size, 10);
assert_eq!(
specs
.iter()
.map(|spec| (spec.src_offset, spec.src_size))
.collect::<Vec<_>>(),
vec![(0, 3), (3, 4), (7, 3)]
);
}
#[test]
fn progress_labels_match_c_basename_and_tail_rules() {
assert_eq!(display_label("/tmp/input"), "input");
assert_eq!(
display_label("/tmp/01234567890123456789"),
"34567890123456789"
);
}
}