diff -r c2d2e5b65def -r 0250e45040f1 mercurial/revlog.py --- a/mercurial/revlog.py Fri Oct 27 02:57:09 2023 +0200 +++ b/mercurial/revlog.py Fri Oct 27 08:54:41 2023 +0200 @@ -295,6 +295,12 @@ # How much data to read and cache into the raw revlog data cache. chunk_cache_size = attr.ib(default=65536) + # The size of the uncompressed cache compared to the largest revision seen. + uncompressed_cache_factor = attr.ib(default=None) + + # The number of chunk cached + uncompressed_cache_count = attr.ib(default=None) + # Allow sparse reading of the revlog data with_sparse_read = attr.ib(default=False) # minimal density of a sparse read chunk @@ -396,6 +402,18 @@ # 3-tuple of (node, rev, text) for a raw revision. self._revisioncache = None + # cache some uncompressed chunks + # rev → uncompressed_chunk + # + # the max cost is dynamically updated to be proportionnal to the + # size of revision we actually encounter. + self._uncompressed_chunk_cache = None + if self.data_config.uncompressed_cache_factor is not None: + self._uncompressed_chunk_cache = util.lrucachedict( + self.data_config.uncompressed_cache_count, + maxcost=65536, # some arbitrary initial value + ) + self._delay_buffer = None @property @@ -414,6 +432,8 @@ def clear_cache(self): assert not self.is_delaying self._revisioncache = None + if self._uncompressed_chunk_cache is not None: + self._uncompressed_chunk_cache.clear() self._segmentfile.clear_cache() self._segmentfile_sidedata.clear_cache() @@ -865,18 +885,26 @@ Returns a str holding uncompressed data for the requested revision. """ + if self._uncompressed_chunk_cache is not None: + uncomp = self._uncompressed_chunk_cache.get(rev) + if uncomp is not None: + return uncomp + compression_mode = self.index[rev][10] data = self.get_segment_for_revs(rev, rev)[1] if compression_mode == COMP_MODE_PLAIN: - return data + uncomp = data elif compression_mode == COMP_MODE_DEFAULT: - return self._decompressor(data) + uncomp = self._decompressor(data) elif compression_mode == COMP_MODE_INLINE: - return self.decompress(data) + uncomp = self.decompress(data) else: msg = b'unknown compression mode %d' msg %= compression_mode raise error.RevlogError(msg) + if self._uncompressed_chunk_cache is not None: + self._uncompressed_chunk_cache.insert(rev, uncomp, cost=len(uncomp)) + return uncomp def _chunks(self, revs, targetsize=None): """Obtain decompressed chunks for the specified revisions. @@ -899,17 +927,30 @@ iosize = self.index.entry_size buffer = util.buffer - l = [] - ladd = l.append + fetched_revs = [] + fadd = fetched_revs.append + chunks = [] ladd = chunks.append - if not self.data_config.with_sparse_read: - slicedchunks = (revs,) + if self._uncompressed_chunk_cache is None: + fetched_revs = revs + else: + for rev in revs: + cached_value = self._uncompressed_chunk_cache.get(rev) + if cached_value is None: + fadd(rev) + else: + ladd((rev, cached_value)) + + if not fetched_revs: + slicedchunks = () + elif not self.data_config.with_sparse_read: + slicedchunks = (fetched_revs,) else: slicedchunks = deltautil.slicechunk( self, - revs, + fetched_revs, targetsize=targetsize, ) @@ -949,7 +990,10 @@ msg %= comp_mode raise error.RevlogError(msg) ladd((rev, c)) - + if self._uncompressed_chunk_cache is not None: + self._uncompressed_chunk_cache.insert(rev, c, len(c)) + + chunks.sort() return [x[1] for x in chunks] def raw_text(self, node, rev): @@ -981,6 +1025,14 @@ if 0 <= rawsize: targetsize = 4 * rawsize + if self._uncompressed_chunk_cache is not None: + # dynamically update the uncompressed_chunk_cache size to the + # largest revision we saw in this revlog. + factor = self.data_config.uncompressed_cache_factor + candidate_size = rawsize * factor + if candidate_size > self._uncompressed_chunk_cache.maxcost: + self._uncompressed_chunk_cache.maxcost = candidate_size + bins = self._chunks(chain, targetsize=targetsize) if basetext is None: basetext = bytes(bins[0])