comparison mercurial/util.py @ 26480:6ae14d1ca3aa

util.chunkbuffer: avoid extra mutations when reading partial chunks Previously, a read(N) where N was less than the length of the first available chunk would mutate the deque instance twice and allocate a new str from the slice of the existing chunk. Profiling drawed my attention to these as a potential hot spot during changegroup reading. This patch makes the code more complicated in order to avoid the aforementioned 3 operations. On a pre-generated mozilla-central gzip bundle, this series has the following impact on `hg unbundle` performance on my MacBook Pro: before: 358.21 real 317.69 user 38.49 sys after: 301.57 real 262.69 user 37.11 sys delta: -56.64 real -55.00 user -1.38 sys
author Gregory Szorc <gregory.szorc@gmail.com>
date Mon, 05 Oct 2015 17:36:32 -0700
parents 46143f31290e
children 7d132557e44a
comparison
equal deleted inserted replaced
26479:46143f31290e 26480:6ae14d1ca3aa
1284 pos = end 1284 pos = end
1285 else: 1285 else:
1286 yield chunk 1286 yield chunk
1287 self.iter = splitbig(in_iter) 1287 self.iter = splitbig(in_iter)
1288 self._queue = collections.deque() 1288 self._queue = collections.deque()
1289 self._chunkoffset = 0
1289 1290
1290 def read(self, l=None): 1291 def read(self, l=None):
1291 """Read L bytes of data from the iterator of chunks of data. 1292 """Read L bytes of data from the iterator of chunks of data.
1292 Returns less than L bytes if the iterator runs dry. 1293 Returns less than L bytes if the iterator runs dry.
1293 1294
1308 if target <= 0: 1309 if target <= 0:
1309 break 1310 break
1310 if not queue: 1311 if not queue:
1311 break 1312 break
1312 1313
1314 # The easy way to do this would be to queue.popleft(), modify the
1315 # chunk (if necessary), then queue.appendleft(). However, for cases
1316 # where we read partial chunk content, this incurs 2 dequeue
1317 # mutations and creates a new str for the remaining chunk in the
1318 # queue. Our code below avoids this overhead.
1319
1313 chunk = queue[0] 1320 chunk = queue[0]
1314 chunkl = len(chunk) 1321 chunkl = len(chunk)
1322 offset = self._chunkoffset
1315 1323
1316 # Use full chunk. 1324 # Use full chunk.
1317 if left >= chunkl: 1325 if offset == 0 and left >= chunkl:
1318 left -= chunkl 1326 left -= chunkl
1319 queue.popleft() 1327 queue.popleft()
1320 buf.append(chunk) 1328 buf.append(chunk)
1329 # self._chunkoffset remains at 0.
1330 continue
1331
1332 chunkremaining = chunkl - offset
1333
1334 # Use all of unconsumed part of chunk.
1335 if left >= chunkremaining:
1336 left -= chunkremaining
1337 queue.popleft()
1338 # offset == 0 is enabled by block above, so this won't merely
1339 # copy via ``chunk[0:]``.
1340 buf.append(chunk[offset:])
1341 self._chunkoffset = 0
1342
1321 # Partial chunk needed. 1343 # Partial chunk needed.
1322 else: 1344 else:
1323 left -= chunkl 1345 buf.append(chunk[offset:offset + left])
1324 queue.popleft() 1346 self._chunkoffset += left
1325 queue.appendleft(chunk[left:]) 1347 left -= chunkremaining
1326 buf.append(chunk[:left])
1327 1348
1328 return ''.join(buf) 1349 return ''.join(buf)
1329 1350
1330 def filechunkiter(f, size=65536, limit=None): 1351 def filechunkiter(f, size=65536, limit=None):
1331 """Create a generator that produces the data in the file size 1352 """Create a generator that produces the data in the file size