comparison contrib/perf.py @ 39568:842cd0bdda75

util: teach lrucachedict to enforce a max total cost Now that lrucachedict entries can have a numeric cost associated with them and we can easily pop the oldest item in the cache, it now becomes relatively trivial to implement support for enforcing a high water mark on the total cost of items in the cache. This commit teaches lrucachedict instances to have a max cost associated with them. When items are inserted, we pop old items until enough "cost" frees up to make room for the new item. This feature is close to zero cost when not used (modulo the insertion regressed introduced by the previous commit): $ ./hg perflrucachedict --size 4 --gets 1000000 --sets 1000000 --mixed 1000000 ! gets ! wall 0.607444 comb 0.610000 user 0.610000 sys 0.000000 (best of 17) ! wall 0.601653 comb 0.600000 user 0.600000 sys 0.000000 (best of 17) ! inserts ! wall 0.678261 comb 0.680000 user 0.680000 sys 0.000000 (best of 14) ! wall 0.685042 comb 0.680000 user 0.680000 sys 0.000000 (best of 15) ! sets ! wall 0.808770 comb 0.800000 user 0.800000 sys 0.000000 (best of 13) ! wall 0.834241 comb 0.830000 user 0.830000 sys 0.000000 (best of 12) ! mixed ! wall 0.782441 comb 0.780000 user 0.780000 sys 0.000000 (best of 13) ! wall 0.803804 comb 0.800000 user 0.800000 sys 0.000000 (best of 13) $ hg perflrucachedict --size 1000 --gets 1000000 --sets 1000000 --mixed 1000000 ! init ! wall 0.006952 comb 0.010000 user 0.010000 sys 0.000000 (best of 418) ! gets ! wall 0.613350 comb 0.610000 user 0.610000 sys 0.000000 (best of 17) ! wall 0.617415 comb 0.620000 user 0.620000 sys 0.000000 (best of 17) ! inserts ! wall 0.701270 comb 0.700000 user 0.700000 sys 0.000000 (best of 15) ! wall 0.700516 comb 0.700000 user 0.700000 sys 0.000000 (best of 15) ! sets ! wall 0.825720 comb 0.830000 user 0.830000 sys 0.000000 (best of 13) ! wall 0.837946 comb 0.840000 user 0.830000 sys 0.010000 (best of 12) ! mixed ! wall 0.821644 comb 0.820000 user 0.820000 sys 0.000000 (best of 13) ! wall 0.850559 comb 0.850000 user 0.850000 sys 0.000000 (best of 12) I reckon the slight slowdown on insert is due to added if checks. For caches with total cost limiting enabled: $ hg perflrucachedict --size 4 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 100 ! gets w/ cost limit ! wall 0.598737 comb 0.590000 user 0.590000 sys 0.000000 (best of 17) ! inserts w/ cost limit ! wall 1.694282 comb 1.700000 user 1.700000 sys 0.000000 (best of 6) ! mixed w/ cost limit ! wall 1.157655 comb 1.150000 user 1.150000 sys 0.000000 (best of 9) $ hg perflrucachedict --size 1000 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 10000 ! gets w/ cost limit ! wall 0.598526 comb 0.600000 user 0.600000 sys 0.000000 (best of 17) ! inserts w/ cost limit ! wall 37.838315 comb 37.840000 user 37.840000 sys 0.000000 (best of 3) ! mixed w/ cost limit ! wall 18.060198 comb 18.060000 user 18.060000 sys 0.000000 (best of 3) $ hg perflrucachedict --size 1000 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 10000 --mixedgetfreq 90 ! gets w/ cost limit ! wall 0.600024 comb 0.600000 user 0.600000 sys 0.000000 (best of 17) ! inserts w/ cost limit ! wall 37.154547 comb 37.120000 user 37.120000 sys 0.000000 (best of 3) ! mixed w/ cost limit ! wall 4.381602 comb 4.380000 user 4.370000 sys 0.010000 (best of 3) The functions we're benchmarking are slightly different, which could move numbers by a few milliseconds. But the slowdown on insert is too great to be explained by that. The slowness is due to insert heavy operations needing to call popoldest() repeatedly when the cache is at capacity. The next commit will address this. Differential Revision: https://phab.mercurial-scm.org/D4503
author Gregory Szorc <gregory.szorc@gmail.com>
date Thu, 06 Sep 2018 14:04:46 -0700
parents ee087f0d7db5
children 5ccd791344f3
comparison
equal deleted inserted replaced
39567:ee087f0d7db5 39568:842cd0bdda75
1867 svfs = getsvfs(repo) 1867 svfs = getsvfs(repo)
1868 timer(lambda: len(obsolete.obsstore(svfs))) 1868 timer(lambda: len(obsolete.obsstore(svfs)))
1869 fm.end() 1869 fm.end()
1870 1870
1871 @command(b'perflrucachedict', formatteropts + 1871 @command(b'perflrucachedict', formatteropts +
1872 [(b'', b'size', 4, b'size of cache'), 1872 [(b'', b'costlimit', 0, b'maximum total cost of items in cache'),
1873 (b'', b'mincost', 0, b'smallest cost of items in cache'),
1874 (b'', b'maxcost', 100, b'maximum cost of items in cache'),
1875 (b'', b'size', 4, b'size of cache'),
1873 (b'', b'gets', 10000, b'number of key lookups'), 1876 (b'', b'gets', 10000, b'number of key lookups'),
1874 (b'', b'sets', 10000, b'number of key sets'), 1877 (b'', b'sets', 10000, b'number of key sets'),
1875 (b'', b'mixed', 10000, b'number of mixed mode operations'), 1878 (b'', b'mixed', 10000, b'number of mixed mode operations'),
1876 (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')], 1879 (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')],
1877 norepo=True) 1880 norepo=True)
1878 def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000, 1881 def perflrucache(ui, mincost=0, maxcost=100, costlimit=0, size=4,
1879 mixedgetfreq=50, **opts): 1882 gets=10000, sets=10000, mixed=10000, mixedgetfreq=50, **opts):
1880 def doinit(): 1883 def doinit():
1881 for i in xrange(10000): 1884 for i in xrange(10000):
1882 util.lrucachedict(size) 1885 util.lrucachedict(size)
1886
1887 costrange = list(range(mincost, maxcost + 1))
1883 1888
1884 values = [] 1889 values = []
1885 for i in xrange(size): 1890 for i in xrange(size):
1886 values.append(random.randint(0, sys.maxint)) 1891 values.append(random.randint(0, sys.maxint))
1887 1892
1897 d[v] = v 1902 d[v] = v
1898 for key in getseq: 1903 for key in getseq:
1899 value = d[key] 1904 value = d[key]
1900 value # silence pyflakes warning 1905 value # silence pyflakes warning
1901 1906
1907 def dogetscost():
1908 d = util.lrucachedict(size, maxcost=costlimit)
1909 for i, v in enumerate(values):
1910 d.insert(v, v, cost=costs[i])
1911 for key in getseq:
1912 try:
1913 value = d[key]
1914 value # silence pyflakes warning
1915 except KeyError:
1916 pass
1917
1902 # Set mode tests insertion speed with cache eviction. 1918 # Set mode tests insertion speed with cache eviction.
1903 setseq = [] 1919 setseq = []
1920 costs = []
1904 for i in xrange(sets): 1921 for i in xrange(sets):
1905 setseq.append(random.randint(0, sys.maxint)) 1922 setseq.append(random.randint(0, sys.maxint))
1923 costs.append(random.choice(costrange))
1906 1924
1907 def doinserts(): 1925 def doinserts():
1908 d = util.lrucachedict(size) 1926 d = util.lrucachedict(size)
1909 for v in setseq: 1927 for v in setseq:
1910 d.insert(v, v) 1928 d.insert(v, v)
1929
1930 def doinsertscost():
1931 d = util.lrucachedict(size, maxcost=costlimit)
1932 for i, v in enumerate(setseq):
1933 d.insert(v, v, cost=costs[i])
1911 1934
1912 def dosets(): 1935 def dosets():
1913 d = util.lrucachedict(size) 1936 d = util.lrucachedict(size)
1914 for v in setseq: 1937 for v in setseq:
1915 d[v] = v 1938 d[v] = v
1921 if r < mixedgetfreq: 1944 if r < mixedgetfreq:
1922 op = 0 1945 op = 0
1923 else: 1946 else:
1924 op = 1 1947 op = 1
1925 1948
1926 mixedops.append((op, random.randint(0, size * 2))) 1949 mixedops.append((op,
1950 random.randint(0, size * 2),
1951 random.choice(costrange)))
1927 1952
1928 def domixed(): 1953 def domixed():
1929 d = util.lrucachedict(size) 1954 d = util.lrucachedict(size)
1930 1955
1931 for op, v in mixedops: 1956 for op, v, cost in mixedops:
1932 if op == 0: 1957 if op == 0:
1933 try: 1958 try:
1934 d[v] 1959 d[v]
1935 except KeyError: 1960 except KeyError:
1936 pass 1961 pass
1937 else: 1962 else:
1938 d[v] = v 1963 d[v] = v
1939 1964
1965 def domixedcost():
1966 d = util.lrucachedict(size, maxcost=costlimit)
1967
1968 for op, v, cost in mixedops:
1969 if op == 0:
1970 try:
1971 d[v]
1972 except KeyError:
1973 pass
1974 else:
1975 d.insert(v, v, cost=cost)
1976
1940 benches = [ 1977 benches = [
1941 (doinit, b'init'), 1978 (doinit, b'init'),
1942 (dogets, b'gets'),
1943 (doinserts, b'inserts'),
1944 (dosets, b'sets'),
1945 (domixed, b'mixed')
1946 ] 1979 ]
1980
1981 if costlimit:
1982 benches.extend([
1983 (dogetscost, b'gets w/ cost limit'),
1984 (doinsertscost, b'inserts w/ cost limit'),
1985 (domixedcost, b'mixed w/ cost limit'),
1986 ])
1987 else:
1988 benches.extend([
1989 (dogets, b'gets'),
1990 (doinserts, b'inserts'),
1991 (dosets, b'sets'),
1992 (domixed, b'mixed')
1993 ])
1947 1994
1948 for fn, title in benches: 1995 for fn, title in benches:
1949 timer, fm = gettimer(ui, opts) 1996 timer, fm = gettimer(ui, opts)
1950 timer(fn, title=title) 1997 timer(fn, title=title)
1951 fm.end() 1998 fm.end()