feat(mt): move frame progression job aggregation into Rust
Move the multithreaded frame-progression ring scan into Rust so the Rust scheduler owns job-ID masking, ready-job range handling, error-output normalization, and active-worker accounting. Keep C-owned job descriptors and mutex-protected snapshots behind the narrow ZSTDMT_projectJob callback, which preserves the private synchronization boundary while making the aggregation policy directly testable in Rust. Test Plan: - cargo fmt --manifest-path rust/Cargo.toml -- --check - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --lib (691 passed) - ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040 && MAKEFLAGS=-j1 make -B -C programs -j1 zstd - ulimit -v 41943040 && MAKEFLAGS=-j1 make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s (84 tests and both short fuzz rounds passed)
This commit is contained in:
@@ -1957,6 +1957,43 @@ fn frame_progression_add_job(
|
||||
progression
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn frame_progression_with_jobs<F>(
|
||||
consumed: u64,
|
||||
in_buff_filled: usize,
|
||||
produced: u64,
|
||||
current_job_id: c_uint,
|
||||
done_job_id: c_uint,
|
||||
next_job_id: c_uint,
|
||||
job_ready: c_uint,
|
||||
job_id_mask: c_uint,
|
||||
mut project_job: F,
|
||||
) -> ZSTD_frameProgression
|
||||
where
|
||||
F: FnMut(c_uint) -> ZSTDMT_jobProjection,
|
||||
{
|
||||
let mut progression = frame_progression(consumed, in_buff_filled, produced, current_job_id);
|
||||
debug_assert!(job_ready <= 1);
|
||||
let last_job_id = next_job_id.wrapping_add(job_ready);
|
||||
for job_id in done_job_id..last_job_id {
|
||||
let projection = project_job(job_id & job_id_mask);
|
||||
let (produced, flushed) = if ERR_isError(projection.cSize) {
|
||||
(0, 0)
|
||||
} else {
|
||||
(projection.cSize, projection.dstFlushed)
|
||||
};
|
||||
debug_assert!(flushed <= produced);
|
||||
progression = frame_progression_add_job(
|
||||
progression,
|
||||
projection.srcSize,
|
||||
projection.consumed,
|
||||
produced,
|
||||
flushed,
|
||||
);
|
||||
}
|
||||
progression
|
||||
}
|
||||
|
||||
/// Construct the base MT frame progression from C-owned scalar state.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ZSTDMT_rust_frameProgression(
|
||||
@@ -1980,6 +2017,42 @@ pub extern "C" fn ZSTDMT_rust_frameProgressionAddJob(
|
||||
frame_progression_add_job(progression, src_size, consumed, produced, flushed)
|
||||
}
|
||||
|
||||
/// Aggregate the base MT progression with mutex-protected C job snapshots.
|
||||
/// Rust owns only the ring scan and normalization policy; C retains the job
|
||||
/// descriptor layout and takes each descriptor mutex in `projectJob`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTDMT_rust_frameProgressionWithJobs(
|
||||
consumed: u64,
|
||||
in_buff_filled: usize,
|
||||
produced: u64,
|
||||
current_job_id: c_uint,
|
||||
done_job_id: c_uint,
|
||||
next_job_id: c_uint,
|
||||
job_ready: c_uint,
|
||||
job_id_mask: c_uint,
|
||||
opaque: *mut c_void,
|
||||
project_job: Option<ZSTDMT_jobProjectionFn>,
|
||||
) -> ZSTD_frameProgression {
|
||||
let Some(project_job) = project_job else {
|
||||
return frame_progression(consumed, in_buff_filled, produced, current_job_id);
|
||||
};
|
||||
frame_progression_with_jobs(
|
||||
consumed,
|
||||
in_buff_filled,
|
||||
produced,
|
||||
current_job_id,
|
||||
done_job_id,
|
||||
next_job_id,
|
||||
job_ready,
|
||||
job_id_mask,
|
||||
|job_id| {
|
||||
let mut projection = ZSTDMT_jobProjection::default();
|
||||
unsafe { project_job(opaque, job_id, &mut projection) };
|
||||
projection
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn rolling_hash_append(mut hash: u64, input: *const u8, size: usize) -> u64 {
|
||||
for pos in 0..size {
|
||||
@@ -4469,6 +4542,59 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_progression_with_jobs_masks_slots_and_normalizes_errors() {
|
||||
let projections = [
|
||||
ZSTDMT_jobProjection {
|
||||
consumed: 50,
|
||||
cSize: 8,
|
||||
srcSize: 50,
|
||||
dstFlushed: 8,
|
||||
..ZSTDMT_jobProjection::default()
|
||||
},
|
||||
ZSTDMT_jobProjection {
|
||||
consumed: 17,
|
||||
cSize: 42,
|
||||
srcSize: 80,
|
||||
dstFlushed: 10,
|
||||
..ZSTDMT_jobProjection::default()
|
||||
},
|
||||
ZSTDMT_jobProjection {
|
||||
consumed: 30,
|
||||
cSize: ERROR(ZstdErrorCode::SequenceProducerFailed),
|
||||
srcSize: 30,
|
||||
dstFlushed: 29,
|
||||
..ZSTDMT_jobProjection::default()
|
||||
},
|
||||
ZSTDMT_jobProjection {
|
||||
consumed: 5,
|
||||
cSize: 7,
|
||||
srcSize: 40,
|
||||
dstFlushed: 6,
|
||||
..ZSTDMT_jobProjection::default()
|
||||
},
|
||||
];
|
||||
let mut calls = Vec::new();
|
||||
|
||||
let progression = frame_progression_with_jobs(100, 20, 300, 9, 5, 8, 1, 3, |job_id| {
|
||||
calls.push(job_id);
|
||||
projections[job_id as usize]
|
||||
});
|
||||
|
||||
assert_eq!(calls, [1, 2, 3, 0]);
|
||||
assert_eq!(
|
||||
progression,
|
||||
ZSTD_frameProgression {
|
||||
ingested: 320,
|
||||
consumed: 202,
|
||||
produced: 357,
|
||||
flushed: 324,
|
||||
currentJobID: 9,
|
||||
nbActiveWorkers: 2,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_data_in_use_skips_first_round_and_scans_oldest_ring_slot() {
|
||||
let source_a = [0u8; 8];
|
||||
|
||||
Reference in New Issue
Block a user