@@ -1538,6 +1538,98 @@ fn subcompaction_variant(c: &mut Criterion, group_name: &str, level: i32) {
15381538 group. finish ( ) ;
15391539}
15401540
1541+ /// Recursively copies `src` into `dst` (created if missing). Used by the
1542+ /// clean-profile subcompaction bench to clone the pre-compact on-disk state.
1543+ fn copy_dir ( src : & std:: path:: Path , dst : & std:: path:: Path ) -> std:: io:: Result < ( ) > {
1544+ std:: fs:: create_dir_all ( dst) ?;
1545+ for entry in std:: fs:: read_dir ( src) ? {
1546+ let entry = entry?;
1547+ let to = dst. join ( entry. file_name ( ) ) ;
1548+ if entry. file_type ( ) ?. is_dir ( ) {
1549+ copy_dir ( & entry. path ( ) , & to) ?;
1550+ } else {
1551+ std:: fs:: copy ( entry. path ( ) , to) ?;
1552+ }
1553+ }
1554+ Ok ( ( ) )
1555+ }
1556+
1557+ /// Builds our pre-compact subcompaction state (gen-0 compacted to the bottom,
1558+ /// gen-1 overwrite flushed into `COMPACTION_FLUSHES` L0 tables) into `dir`, then
1559+ /// drops the tree so the state is resident on disk for cloning.
1560+ fn build_subcompaction_master ( dir : & std:: path:: Path , level : i32 , inputs : & SubcompactionInputs ) {
1561+ let config = Config :: new (
1562+ dir,
1563+ SequenceNumberCounter :: default ( ) ,
1564+ SequenceNumberCounter :: default ( ) ,
1565+ )
1566+ . data_block_compression_policy ( CompressionPolicy :: all ( CompressionType :: Zstd ( level) ) )
1567+ . compaction_threads ( SUBCOMPACTION_THREADS )
1568+ . subcompaction_min_bytes ( 0 ) ;
1569+ let tree = apply_preset ( config, active_preset ( ) )
1570+ . open ( )
1571+ . expect ( "master: open" ) ;
1572+ let total = inputs. keys . len ( ) as u64 ;
1573+ for ( ( key, value) , seqno) in inputs. keys . iter ( ) . zip ( inputs. values . iter ( ) ) . zip ( 0u64 ..) {
1574+ tree. insert ( key, value, seqno) ;
1575+ }
1576+ tree. flush_active_memtable ( 0 ) . expect ( "master: flush" ) ;
1577+ tree. major_compact ( SUBCOMPACTION_BOTTOM_TARGET , 0 )
1578+ . expect ( "master: bottom compact" ) ;
1579+ let flush_points: Vec < u64 > = ( 1 ..COMPACTION_FLUSHES )
1580+ . map ( |b| ( b * total) / COMPACTION_FLUSHES )
1581+ . collect ( ) ;
1582+ let mut written = 0u64 ;
1583+ for ( ( key, value) , seqno) in inputs. keys . iter ( ) . zip ( inputs. values . iter ( ) ) . zip ( total..) {
1584+ tree. insert ( key, value, seqno) ;
1585+ written += 1 ;
1586+ if flush_points. contains ( & written) {
1587+ tree. flush_active_memtable ( 0 ) . expect ( "master: flush" ) ;
1588+ }
1589+ }
1590+ tree. flush_active_memtable ( 0 ) . expect ( "master: flush" ) ;
1591+ }
1592+
1593+ /// Clean timed-only subcompaction profile (ours). The pre-compact state is built
1594+ /// ONCE into a master dir; each iteration clones it to a fresh dir and times
1595+ /// ONLY `major_compact`. perf-recording this isolates the compaction cost (the
1596+ /// clone + open are distinct symbols), unlike `subcompaction_zstd3` whose
1597+ /// per-iteration input rebuild contaminates the flamegraph.
1598+ fn bench_subcompaction_clean ( c : & mut Criterion ) {
1599+ let level = 3 ;
1600+ let n = 40_000u64 ;
1601+ let inputs = SubcompactionInputs :: build ( n) ;
1602+ let master = tempfile:: tempdir ( ) . expect ( "master tempdir" ) ;
1603+ build_subcompaction_master ( master. path ( ) , level, & inputs) ;
1604+
1605+ let mut group = c. benchmark_group ( "subcompaction_clean" ) ;
1606+ group. bench_function ( BenchmarkId :: new ( "ours" , n) , |b| {
1607+ b. iter_custom ( |iters| {
1608+ let mut elapsed = std:: time:: Duration :: ZERO ;
1609+ for _ in 0 ..iters {
1610+ let work = tempfile:: tempdir ( ) . expect ( "work tempdir" ) ;
1611+ copy_dir ( master. path ( ) , work. path ( ) ) . expect ( "clone master" ) ;
1612+ let config = Config :: new (
1613+ work. path ( ) ,
1614+ SequenceNumberCounter :: default ( ) ,
1615+ SequenceNumberCounter :: default ( ) ,
1616+ )
1617+ . data_block_compression_policy ( CompressionPolicy :: all ( CompressionType :: Zstd ( level) ) )
1618+ . compaction_threads ( SUBCOMPACTION_THREADS )
1619+ . subcompaction_min_bytes ( 0 ) ;
1620+ let tree = apply_preset ( config, active_preset ( ) )
1621+ . open ( )
1622+ . expect ( "work: open" ) ;
1623+ let start = std:: time:: Instant :: now ( ) ;
1624+ tree. major_compact ( u64:: MAX , 0 ) . expect ( "work: compact" ) ;
1625+ elapsed += start. elapsed ( ) ;
1626+ }
1627+ elapsed
1628+ } ) ;
1629+ } ) ;
1630+ group. finish ( ) ;
1631+ }
1632+
15411633/// Sub-compaction head-to-head: our range-parallel split vs RocksDB
15421634/// `max_subcompactions`. Pinned to zstd level 3 — the level RocksDB actually
15431635/// applies to bottommost compaction output (see [`bench_compaction`]) — with
@@ -1555,6 +1647,7 @@ criterion_group!(
15551647 bench_seek_random,
15561648 bench_overwrite,
15571649 bench_compaction,
1558- bench_subcompaction
1650+ bench_subcompaction,
1651+ bench_subcompaction_clean
15591652) ;
15601653criterion_main ! ( benches) ;
0 commit comments