util: lower water mark when removing nodes after cost limit reached
See the inline comment for the reasoning here. This is a pretty
common strategy for garbage collectors, other cache-like primtives.
The performance impact is substantial:
$ hg perflrucachedict --size 4 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 100
! inserts w/ cost limit
! wall 1.659181 comb 1.650000 user 1.650000 sys 0.000000 (best of 7)
! wall 1.722122 comb 1.720000 user 1.720000 sys 0.000000 (best of 6)
! mixed w/ cost limit
! wall 1.139955 comb 1.140000 user 1.140000 sys 0.000000 (best of 9)
! wall 1.182513 comb 1.180000 user 1.180000 sys 0.000000 (best of 9)
$ hg perflrucachedict --size 1000 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 10000
! inserts
! wall 0.679546 comb 0.680000 user 0.680000 sys 0.000000 (best of 15)
! sets
! wall 0.825147 comb 0.830000 user 0.830000 sys 0.000000 (best of 13)
! inserts w/ cost limit
! wall 25.105273 comb 25.080000 user 25.080000 sys 0.000000 (best of 3)
! wall 1.724397 comb 1.720000 user 1.720000 sys 0.000000 (best of 6)
! mixed
! wall 0.807096 comb 0.810000 user 0.810000 sys 0.000000 (best of 13)
! mixed w/ cost limit
! wall 12.104470 comb 12.070000 user 12.070000 sys 0.000000 (best of 3)
! wall 1.190563 comb 1.190000 user 1.190000 sys 0.000000 (best of 9)
$ hg perflrucachedict --size 1000 --gets 1000000 --sets 1000000 --mixed 1000000 --costlimit 10000 --mixedgetfreq 90
! inserts
! wall 0.711177 comb 0.710000 user 0.710000 sys 0.000000 (best of 14)
! sets
! wall 0.846992 comb 0.850000 user 0.850000 sys 0.000000 (best of 12)
! inserts w/ cost limit
! wall 25.963028 comb 25.960000 user 25.960000 sys 0.000000 (best of 3)
! wall 2.184311 comb 2.180000 user 2.180000 sys 0.000000 (best of 5)
! mixed
! wall 0.728256 comb 0.730000 user 0.730000 sys 0.000000 (best of 14)
! mixed w/ cost limit
! wall 3.174256 comb 3.170000 user 3.170000 sys 0.000000 (best of 4)
! wall 0.773186 comb 0.770000 user 0.770000 sys 0.000000 (best of 13)
$ hg perflrucachedict --size 100000 --gets 1000000 --sets 1000000 --mixed 1000000 --mixedgetfreq 90 --costlimit 5000000
! gets
! wall 1.191368 comb 1.190000 user 1.190000 sys 0.000000 (best of 9)
! wall 1.195304 comb 1.190000 user 1.190000 sys 0.000000 (best of 9)
! inserts
! wall 0.950995 comb 0.950000 user 0.950000 sys 0.000000 (best of 11)
! inserts w/ cost limit
! wall 1.589732 comb 1.590000 user 1.590000 sys 0.000000 (best of 7)
! sets
! wall 1.094941 comb 1.100000 user 1.090000 sys 0.010000 (best of 9)
! mixed
! wall 0.936420 comb 0.940000 user 0.930000 sys 0.010000 (best of 10)
! mixed w/ cost limit
! wall 0.882780 comb 0.870000 user 0.870000 sys 0.000000 (best of 11)
This puts us ~2x slower than caches without cost accounting. And for
read-heavy workloads (the prime use cases for caches), performance is
nearly identical.
In the worst case (pure write workloads with cost accounting enabled),
we're looking at ~1.5us per insert on large caches. That seems "fast
enough."
Differential Revision: https://phab.mercurial-scm.org/D4505
$ echo "[extensions]" >> $HGRCPATH
$ echo "mq=" >> $HGRCPATH
$ hg init foo
$ cd foo
$ echo a > a
$ hg ci -qAm a
Default queue:
$ hg qqueue
patches (active)
$ echo b > a
$ hg qnew -fgDU somestuff
Applied patches in default queue:
$ hg qap
somestuff
Try to change patch (create succeeds, switch fails):
$ hg qqueue foo --create
abort: new queue created, but cannot make active as patches are applied
[255]
$ hg qqueue
foo
patches (active)
Empty default queue:
$ hg qpop
popping somestuff
patch queue now empty
Switch queue:
$ hg qqueue foo
$ hg qqueue
foo (active)
patches
List queues, quiet:
$ hg qqueue --quiet
foo
patches
Fail creating queue with already existing name:
$ hg qqueue --create foo
abort: queue "foo" already exists
[255]
$ hg qqueue
foo (active)
patches
Create new queue for rename:
$ hg qqueue --create bar
$ hg qqueue
bar (active)
foo
patches
Rename queue, same name:
$ hg qqueue --rename bar
abort: can't rename "bar" to its current name
[255]
Rename queue to existing:
$ hg qqueue --rename foo
abort: queue "foo" already exists
[255]
Rename queue:
$ hg qqueue --rename buz
$ hg qqueue
buz (active)
foo
patches
Switch back to previous queue:
$ hg qqueue foo
$ hg qqueue --delete buz
$ hg qqueue
foo (active)
patches
Create queue for purge:
$ hg qqueue --create purge-me
$ hg qqueue
foo
patches
purge-me (active)
Create patch for purge:
$ hg qnew patch-purge-me
$ ls -1d .hg/patches-purge-me 2>/dev/null || true
.hg/patches-purge-me
$ hg qpop -a
popping patch-purge-me
patch queue now empty
Purge queue:
$ hg qqueue foo
$ hg qqueue --purge purge-me
$ hg qqueue
foo (active)
patches
$ ls -1d .hg/patches-purge-me 2>/dev/null || true
Unapplied patches:
$ hg qun
$ echo c > a
$ hg qnew -fgDU otherstuff
Fail switching back:
$ hg qqueue patches
abort: new queue created, but cannot make active as patches are applied
[255]
Fail deleting current:
$ hg qqueue foo --delete
abort: cannot delete currently active queue
[255]
Switch back and delete foo:
$ hg qpop -a
popping otherstuff
patch queue now empty
$ hg qqueue patches
$ hg qqueue foo --delete
$ hg qqueue
patches (active)
Tricky cases:
$ hg qqueue store --create
$ hg qnew journal
$ hg qqueue
patches
store (active)
$ hg qpop -a
popping journal
patch queue now empty
$ hg qqueue patches
$ hg qun
somestuff
Invalid names:
$ hg qqueue test/../../bar --create
abort: invalid queue name, may not contain the characters ":\/."
[255]
$ hg qqueue . --create
abort: invalid queue name, may not contain the characters ":\/."
[255]
$ cd ..