@@ -98,6 +98,8 @@ struct segmenter_stats {
9898 uint64_t bloom_lookups{0 };
9999 uint64_t bloom_hits{0 };
100100 uint64_t bloom_true_positives{0 };
101+ uint64_t total_appends{0 };
102+ uint64_t total_appends_with_cached_hash{0 };
101103 value_stream_quantile_estimator l2_collision_vec_size{0.5 , 0.75 , 0.9 , 0.95 ,
102104 0.99 };
103105};
@@ -403,7 +405,9 @@ class ConstantGranularityPolicy : private GranularityPolicyBase {
403405 return kGranularity ;
404406 }
405407
406- static DWARFS_FORCE_INLINE bool compile_time_granularity () { return true ; }
408+ static constexpr DWARFS_FORCE_INLINE bool compile_time_granularity () {
409+ return true ;
410+ }
407411};
408412
409413class VariableGranularityPolicy : private GranularityPolicyBase {
@@ -471,7 +475,9 @@ class VariableGranularityPolicy : private GranularityPolicyBase {
471475 return granularity_;
472476 }
473477
474- static DWARFS_FORCE_INLINE bool compile_time_granularity () { return false ; }
478+ static constexpr DWARFS_FORCE_INLINE bool compile_time_granularity () {
479+ return false ;
480+ }
475481
476482 private:
477483 uint_fast32_t const granularity_;
@@ -1183,10 +1189,12 @@ class active_block : private GranularityPolicy {
11831189
11841190 DWARFS_FORCE_INLINE mutable_byte_buffer data () const { return data_; }
11851191
1192+ // returns true if cached_hashval was used to avoid recomputation
11861193 template <bool UseSegmentQueue>
1187- DWARFS_FORCE_INLINE void append_bytes (
1194+ DWARFS_FORCE_INLINE bool append_bytes (
11881195 granular_extent_adapter<GranularityPolicy, UseSegmentQueue>& data,
1189- file_off_t offset, file_size_t size, bloom_filter& global_filter);
1196+ file_off_t offset, file_size_t size, bloom_filter& global_filter,
1197+ std::optional<rsync_hash::value_type> cached_hashval);
11901198
11911199 DWARFS_FORCE_INLINE size_t next_hash_distance_in_frames () const {
11921200 return window_step_mask_ + 1 - (size_in_frames () & window_step_mask_);
@@ -1341,10 +1349,13 @@ class segmenter_ final : public segmenter::impl, private SegmentingPolicy {
13411349 template <bool UseSegmentQueue>
13421350 DWARFS_FORCE_INLINE void
13431351 append_to_block (chunkable& chkable, extent_adapter_t <UseSegmentQueue>& data,
1344- file_off_t offset_in_frames, file_size_t size_in_frames);
1352+ file_off_t offset_in_frames, file_size_t size_in_frames,
1353+ std::optional<rsync_hash::value_type> cached_hashval);
13451354 template <bool UseSegmentQueue>
1346- void add_data (chunkable& chkable, extent_adapter_t <UseSegmentQueue>& data,
1347- file_off_t offset_in_frames, file_size_t size_in_frames);
1355+ void
1356+ add_data (chunkable& chkable, extent_adapter_t <UseSegmentQueue>& data,
1357+ file_off_t offset_in_frames, file_size_t size_in_frames,
1358+ std::optional<rsync_hash::value_type> cached_hashval = std::nullopt );
13481359 template <bool UseSegmentQueue>
13491360 DWARFS_FORCE_INLINE void
13501361 segment_and_add_data (chunkable& chkable,
@@ -1483,27 +1494,74 @@ active_block<LoggerPolicy, GranularityPolicy>::is_existing_repeating_sequence(
14831494
14841495template <typename LoggerPolicy, typename GranularityPolicy>
14851496template <bool UseSegmentQueue>
1486- DWARFS_FORCE_INLINE void
1497+ DWARFS_FORCE_INLINE bool
14871498active_block<LoggerPolicy, GranularityPolicy>::append_bytes(
14881499 granular_extent_adapter<GranularityPolicy, UseSegmentQueue>& data,
1489- file_off_t data_offset, file_size_t data_size,
1490- bloom_filter& global_filter ) {
1500+ file_off_t data_offset, file_size_t data_size, bloom_filter& global_filter,
1501+ std::optional<rsync_hash::value_type> cached_hashval ) {
14911502 auto v = this ->template create <granular_buffer_adapter<GranularityPolicy>>(
14921503 data_.raw_buffer ());
14931504
14941505 // TODO: this works in theory, but slows down the segmenter by almost 10%
14951506 // auto v = this->template create<
14961507 // granular_byte_buffer_adapter<GranularityPolicy>>(data_);
14971508
1509+ auto const data_frames = bytes_to_frames (data_size);
14981510 auto offset = v.size ();
14991511
1500- DWARFS_CHECK (offset + bytes_to_frames (data_size) <= capacity_in_frames_,
1512+ DWARFS_CHECK (offset + data_frames <= capacity_in_frames_,
15011513 fmt::format (" block capacity exceeded: {} + {} > {}" , offset,
15021514 data_size, frames_to_bytes (capacity_in_frames_)));
15031515
15041516 v.append (data, data_offset, data_size);
15051517
1518+ auto insert_hashval = [&](size_t offset) {
1519+ auto const hashval = hasher_ ();
1520+ if (!is_existing_repeating_sequence (hashval, offset - window_size_))
1521+ [[likely]] {
1522+ offsets_.insert (hashval, offset - window_size_);
1523+ if (filter_.size () > 0 ) {
1524+ filter_.add (hashval);
1525+ }
1526+ global_filter.add (hashval);
1527+ }
1528+ };
1529+
15061530 if (window_size_ > 0 ) {
1531+ /*
1532+ * If a cached hash value is provided *and* we are already at least
1533+ * window_size_ frames into the block, we can skip the cyclic hash
1534+ * computation and reuse the cached hash value. `segment_and_add_data`
1535+ * guarantees that it will only set a cached hash value for a segment
1536+ * with `window_step` frames and aligned to a `window_step` offset.
1537+ * However, `add_data` could still split that segment if it would
1538+ * cross a block boundary, which is why we need additional checks.
1539+ *
1540+ * For a granularity that is a power of two, these checks can never
1541+ * fail, since the block size is always a multiple of the step size
1542+ * in bytes.
1543+ */
1544+
1545+ auto is_complete_segment = [&] {
1546+ if constexpr (GranularityPolicy::compile_time_granularity ()) {
1547+ if constexpr (std::has_single_bit (GranularityPolicy::kGranularity )) {
1548+ assert (std::cmp_equal (data_frames, window_step_mask_ + 1 ) &&
1549+ (offset & window_step_mask_) == 0 );
1550+ return true ;
1551+ }
1552+ }
1553+ return std::cmp_equal (data_frames, window_step_mask_ + 1 ) &&
1554+ (offset & window_step_mask_) == 0 ;
1555+ };
1556+
1557+ if (cached_hashval.has_value () && offset >= window_size_ &&
1558+ is_complete_segment ()) {
1559+ offset += data_frames;
1560+ hasher_.set (*cached_hashval);
1561+ insert_hashval (offset);
1562+ return true ;
1563+ }
1564+
15071565 while (offset < v.size ()) {
15081566 if (offset < window_size_) [[unlikely]] {
15091567 v.update_hash (hasher_, offset);
@@ -1512,19 +1570,13 @@ active_block<LoggerPolicy, GranularityPolicy>::append_bytes(
15121570 }
15131571 if (++offset >= window_size_) [[likely]] {
15141572 if ((offset & window_step_mask_) == 0 ) [[unlikely]] {
1515- auto hashval = hasher_ ();
1516- if (!is_existing_repeating_sequence (hashval, offset - window_size_))
1517- [[likely]] {
1518- offsets_.insert (hashval, offset - window_size_);
1519- if (filter_.size () > 0 ) {
1520- filter_.add (hashval);
1521- }
1522- global_filter.add (hashval);
1523- }
1573+ insert_hashval (offset);
15241574 }
15251575 }
15261576 }
15271577 }
1578+
1579+ return false ;
15281580}
15291581
15301582template <typename LoggerPolicy, typename GranularityPolicy>
@@ -1655,6 +1707,15 @@ void segmenter_<LoggerPolicy, SegmentingPolicy>::finish() {
16551707 " avoided {} collisions in 0x{:02x}-byte sequences" , v,
16561708 k);
16571709 }
1710+
1711+ if (stats_.total_appends > 0 ) {
1712+ LOG_VERBOSE << cfg_.context
1713+ << fmt::format (" appends using cached hashes: {}/{} ({:.3f}%)" ,
1714+ stats_.total_appends_with_cached_hash ,
1715+ stats_.total_appends ,
1716+ 100.0 * stats_.total_appends_with_cached_hash /
1717+ stats_.total_appends );
1718+ }
16581719}
16591720
16601721template <typename LoggerPolicy, typename SegmentingPolicy>
@@ -1671,7 +1732,8 @@ template <bool UseSegmentQueue>
16711732DWARFS_FORCE_INLINE void
16721733segmenter_<LoggerPolicy, SegmentingPolicy>::append_to_block(
16731734 chunkable& chkable, extent_adapter_t <UseSegmentQueue>& data,
1674- file_off_t offset_in_frames, file_size_t size_in_frames) {
1735+ file_off_t offset_in_frames, file_size_t size_in_frames,
1736+ std::optional<rsync_hash::value_type> cached_hashval) {
16751737 if (blocks_.empty () or blocks_.back ().full ()) [[unlikely]] {
16761738 if (blocks_.size () >= std::max<size_t >(1 , cfg_.max_active_blocks )) {
16771739 blocks_.pop_front ();
@@ -1702,7 +1764,13 @@ segmenter_<LoggerPolicy, SegmentingPolicy>::append_to_block(
17021764 << frames_to_bytes (block.size_in_frames ())
17031765 << " from chunkable offset " << offset_in_bytes;
17041766
1705- block.append_bytes (data, offset_in_bytes, size_in_bytes, global_filter_);
1767+ ++stats_.total_appends ;
1768+
1769+ if (block.append_bytes (data, offset_in_bytes, size_in_bytes, global_filter_,
1770+ cached_hashval)) {
1771+ ++stats_.total_appends_with_cached_hash ;
1772+ }
1773+
17061774 chunk_.size_in_frames += size_in_frames;
17071775
17081776 prog_.filesystem_size += size_in_bytes;
@@ -1718,7 +1786,8 @@ template <typename LoggerPolicy, typename SegmentingPolicy>
17181786template <bool UseSegmentQueue>
17191787void segmenter_<LoggerPolicy, SegmentingPolicy>::add_data(
17201788 chunkable& chkable, extent_adapter_t <UseSegmentQueue>& data,
1721- file_off_t offset_in_frames, file_size_t size_in_frames) {
1789+ file_off_t offset_in_frames, file_size_t size_in_frames,
1790+ std::optional<rsync_hash::value_type> cached_hashval) {
17221791 while (size_in_frames > 0 ) {
17231792 file_off_t block_offset_in_frames = 0 ;
17241793
@@ -1729,10 +1798,13 @@ void segmenter_<LoggerPolicy, SegmentingPolicy>::add_data(
17291798 auto const chunk_size_in_frames = std::min<file_size_t >(
17301799 size_in_frames, block_size_in_frames_ - block_offset_in_frames);
17311800
1732- append_to_block (chkable, data, offset_in_frames, chunk_size_in_frames);
1801+ append_to_block (chkable, data, offset_in_frames, chunk_size_in_frames,
1802+ cached_hashval);
17331803
17341804 offset_in_frames += chunk_size_in_frames;
17351805 size_in_frames -= chunk_size_in_frames;
1806+
1807+ cached_hashval.reset (); // only use for first append
17361808 }
17371809}
17381810
@@ -1763,6 +1835,19 @@ segmenter_<LoggerPolicy, SegmentingPolicy>::segment_and_add_data(
17631835 lookback_size_in_frames +
17641836 (blocks_.empty () ? window_step_
17651837 : blocks_.back ().next_hash_distance_in_frames ());
1838+ /*
1839+ * The lagging hashvals vector is used to store the hash values every
1840+ * window_step_ frames that reach back up to lookback_size_in_frames.
1841+ * In most cases, the oldest of these lagging hash values can be used
1842+ * to avoid recomputing the cyclic hash when copying data to the current
1843+ * block.
1844+ */
1845+ std::vector<rsync_hash::value_type> lagging_hashvals (lookback_size_in_frames /
1846+ window_step_);
1847+ size_t lagging_hashvals_index = 0 ;
1848+ size_t lagging_hashvals_count = 0 ;
1849+
1850+ assert (lookback_size_in_frames % window_step_ == 0 );
17661851
17671852 DWARFS_CHECK (std::cmp_greater_equal (size_in_frames, window_size_),
17681853 " unexpected call to segment_and_add_data" );
@@ -1790,8 +1875,10 @@ segmenter_<LoggerPolicy, SegmentingPolicy>::segment_and_add_data(
17901875 last_offset = offset;
17911876 };
17921877
1793- auto add_frames = [&](file_size_t num) {
1794- add_data (chkable, data, frames_written, num);
1878+ auto add_frames = [&](file_size_t num,
1879+ std::optional<rsync_hash::value_type> cached_hv =
1880+ std::nullopt ) {
1881+ add_data (chkable, data, frames_written, num, cached_hv);
17951882 };
17961883
17971884 while (offset_in_frames < size_in_frames) {
@@ -1882,6 +1969,9 @@ segmenter_<LoggerPolicy, SegmentingPolicy>::segment_and_add_data(
18821969 next_hash_offset_in_frames =
18831970 frames_written + lookback_size_in_frames +
18841971 blocks_.back ().next_hash_distance_in_frames ();
1972+
1973+ // must reset lagging hashvals after a successful match
1974+ lagging_hashvals_count = 0 ;
18851975 }
18861976
18871977 matches.clear ();
@@ -1899,11 +1989,26 @@ segmenter_<LoggerPolicy, SegmentingPolicy>::segment_and_add_data(
18991989 [[unlikely]] {
19001990 auto num_to_write =
19011991 offset_in_frames - lookback_size_in_frames - frames_written;
1902- add_frames (num_to_write);
1992+ std::optional<rsync_hash::value_type> cached_hv;
1993+
1994+ // only if we have enough lagging hash values to cover the lookback size
1995+ // can we use the oldest one as a cached value
1996+ if (lagging_hashvals_count >= lagging_hashvals.size ()) {
1997+ cached_hv = lagging_hashvals[lagging_hashvals_index];
1998+ }
1999+
2000+ add_frames (num_to_write, cached_hv);
19032001 frames_written += num_to_write;
19042002 next_hash_offset_in_frames += window_step_;
19052003
19062004 update_progress (offset_in_frames);
2005+
2006+ lagging_hashvals[lagging_hashvals_index] = hashval;
2007+ ++lagging_hashvals_count;
2008+
2009+ if (++lagging_hashvals_index == lagging_hashvals.size ()) {
2010+ lagging_hashvals_index = 0 ;
2011+ }
19072012 }
19082013
19092014 offset_in_frames = hashwin.slide (hasher, data, offset_in_frames);
0 commit comments