smartset: convert set to list lazily
If the caller only wants to construct a baseset via a set, and then do
"__contains__" tests. It's unnecessary to initialize the list.
Testing on my unfiltered hg-committed repo where len(draft()) is 2600, this
patch shows about 6% improvement on set intensive queries:
Before:
$ for i in `seq 5`; hg perfrevset 'draft() & draft() & draft() & draft()'
! wall 0.001196 comb 0.000000 user 0.000000 sys 0.000000 (best of 2011)
! wall 0.001191 comb 0.000000 user 0.000000 sys 0.000000 (best of 2099)
! wall 0.001186 comb 0.010000 user 0.010000 sys 0.000000 (best of 1953)
! wall 0.001182 comb 0.000000 user 0.000000 sys 0.000000 (best of 2135)
! wall 0.001193 comb 0.000000 user 0.000000 sys 0.000000 (best of 2177)
After:
$ for i in `seq 5`; hg perfrevset 'draft() & draft() & draft() & draft()'
! wall 0.001128 comb 0.000000 user 0.000000 sys 0.000000 (best of 2247)
! wall 0.001119 comb 0.000000 user 0.000000 sys 0.000000 (best of 2317)
! wall 0.001115 comb 0.000000 user 0.000000 sys 0.000000 (best of 2244)
! wall 0.001131 comb 0.000000 user 0.000000 sys 0.000000 (best of 2093)
! wall 0.001124 comb 0.000000 user 0.000000 sys 0.000000 (best of 2134)
It could have bigger impact on larger sets in theory.
--- a/mercurial/smartset.py Thu Feb 16 11:34:50 2017 -0500
+++ b/mercurial/smartset.py Fri Feb 17 20:59:29 2017 -0800
@@ -171,8 +171,12 @@
self._set = data
# set has no order we pick one for stability purpose
self._ascending = True
- data = list(data)
- self._list = data
+ # converting set to list has a cost, do it lazily
+ data = None
+ else:
+ data = list(data)
+ if data is not None:
+ self._list = data
self._datarepr = datarepr
@util.propertycache
@@ -185,6 +189,12 @@
asclist.sort()
return asclist
+ @util.propertycache
+ def _list(self):
+ # _list is only lazily constructed if we have _set
+ assert '_set' in self.__dict__
+ return list(self._set)
+
def __iter__(self):
if self._ascending is None:
return iter(self._list)
@@ -204,7 +214,7 @@
return self._set.__contains__
def __nonzero__(self):
- return bool(self._list)
+ return bool(len(self))
def sort(self, reverse=False):
self._ascending = not bool(reverse)
@@ -218,7 +228,10 @@
self._istopo = False
def __len__(self):
- return len(self._list)
+ if '_list' in self.__dict__:
+ return len(self._list)
+ else:
+ return len(self._set)
def isascending(self):
"""Returns True if the collection is ascending order, False if not.