diff --git a/zstd-wasm/bench/bench.mjs b/zstd-wasm/bench/bench.mjs index 7952feee1..f7abdafd6 100644 --- a/zstd-wasm/bench/bench.mjs +++ b/zstd-wasm/bench/bench.mjs @@ -166,7 +166,7 @@ for (const s of ["sample-1.service", "sample-2.service"]) { console.log("\n=== dictionary compress/decompress vs @bokuweb/zstd-wasm ==="); const dictRows = []; for (const [scenario, data] of dictSamples) { - for (const level of [3, 19]) { + for (const level of LEVELS) { for (const [name, eng] of Object.entries(engines)) { const framed = eng.compressUsingDict(data, dict, level); const ok = eq(eng.decompressUsingDict(framed, dict), data); @@ -184,7 +184,7 @@ for (const [scenario, data] of dictSamples) { } for (const [scenario, data] of dictSamples) { console.log(`\n${scenario} + dict (${data.length} bytes, dict ${dict.length})`); - for (const level of [3, 19]) { + for (const level of LEVELS) { const at = (n) => dictRows.find((r) => r.scenario === scenario && r.level === level && r.name === n); const b = at("bokuweb"), s = at("ours-simd128"), sc = at("ours-scalar"); const x = (o) => (o.cNs / b.cNs).toFixed(2), xd = (o) => (o.dNs / b.dNs).toFixed(2); diff --git a/zstd/src/encoding/match_generator.rs b/zstd/src/encoding/match_generator.rs index 98dd561da..d0f74730d 100644 --- a/zstd/src/encoding/match_generator.rs +++ b/zstd/src/encoding/match_generator.rs @@ -2640,8 +2640,10 @@ impl Matcher for MatchGeneratorDriver { // clears an existing producer's table but does not null the // slot — installing here gives the new frame a fresh producer. #[cfg(feature = "hash")] - if let MatcherStorage::HashChain(hc) = &mut self.storage { - let producer = self + { + // Resolve the derived LDM params first (immutable borrow of the + // overrides), then reuse the existing producer's allocation below. + let derived_ldm = self .param_overrides .as_ref() .and_then(|ov| ov.ldm) @@ -2660,9 +2662,27 @@ impl Matcher for MatchGeneratorDriver { min_match_length: ldm_ov.min_match.unwrap_or(0), bucket_size_log: ldm_ov.bucket_size_log.unwrap_or(0), }; - super::ldm::LdmProducer::new(seed.derive(strategy_ord)) + seed.derive(strategy_ord) + }); + if let MatcherStorage::HashChain(hc) = &mut self.storage { + // Reuse the existing producer's hash-table allocation when the + // derived params are unchanged: only `clear()` (re-zero the + // table + re-seed the rolling hash, no allocation) is needed for + // the new frame. A params change (or the first frame) forces a + // fresh `LdmProducer::new`. On the reused-encoder compress-dict + // path this avoids re-allocating the LDM hash table (large at + // btultra2) every frame — upstream zstd reuses its `ldmState_t` + // the same way. `clear()` is mandatory here for correctness + // regardless of what `BtMatcher::reset` did to the old table. + let producer = derived_ldm.map(|p| match hc.take_ldm_producer() { + Some(mut existing) if existing.params() == p => { + existing.clear(); + existing + } + _ => super::ldm::LdmProducer::new(p), }); - hc.set_ldm_producer(producer); + hc.set_ldm_producer(producer); + } } // Record the resolved matcher shape for the primed-snapshot key. Captured // here (post-resolution, after the test-only param override) so the key