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
$ heads()
> {
> hg heads --template '{rev}: {desc|firstline|strip} ({branches})\n' "$@"
> }
$ hg init a
$ cd a
$ echo 'root' >root
$ hg add root
$ hg commit -m "Adding root node"
$ heads
0: Adding root node ()
-------
$ heads .
0: Adding root node ()
=======
$ echo 'a' >a
$ hg add a
$ hg branch a
marked working directory as branch a
(branches are permanent and global, did you want a bookmark?)
$ hg commit -m "Adding a branch"
$ heads
1: Adding a branch (a)
0: Adding root node ()
-------
$ heads .
1: Adding a branch (a)
=======
$ hg update -C 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ echo 'b' >b
$ hg add b
$ hg branch b
marked working directory as branch b
$ hg commit -m "Adding b branch"
$ heads
2: Adding b branch (b)
1: Adding a branch (a)
0: Adding root node ()
-------
$ heads .
2: Adding b branch (b)
=======
$ echo 'bh1' >bh1
$ hg add bh1
$ hg commit -m "Adding b branch head 1"
$ heads
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
$ heads .
3: Adding b branch head 1 (b)
=======
$ hg update -C 2
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ echo 'bh2' >bh2
$ hg add bh2
$ hg commit -m "Adding b branch head 2"
created new head
$ heads
4: Adding b branch head 2 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
$ heads .
4: Adding b branch head 2 (b)
3: Adding b branch head 1 (b)
=======
$ hg update -C 2
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ echo 'bh3' >bh3
$ hg add bh3
$ hg commit -m "Adding b branch head 3"
created new head
$ heads
5: Adding b branch head 3 (b)
4: Adding b branch head 2 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
$ heads .
5: Adding b branch head 3 (b)
4: Adding b branch head 2 (b)
3: Adding b branch head 1 (b)
=======
$ hg merge 4
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ hg commit -m "Merging b branch head 2 and b branch head 3"
$ heads
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
$ heads .
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
=======
$ echo 'c' >c
$ hg add c
$ hg branch c
marked working directory as branch c
$ hg commit -m "Adding c branch"
$ heads
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
$ heads .
7: Adding c branch (c)
=======
$ heads -r 3 .
no open branch heads found on branches c (started at 3)
[1]
$ heads -r 2 .
7: Adding c branch (c)
-------
$ hg update -C 4
0 files updated, 0 files merged, 2 files removed, 0 files unresolved
-------
$ heads -r 3 .
3: Adding b branch head 1 (b)
-------
$ heads -r 2 .
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
-------
$ heads -r 7 .
no open branch heads found on branches b (started at 7)
[1]
=======
$ for i in 0 1 2 3 4 5 6 7; do
> hg update -C "$i"
> heads
> echo '-------'
> heads .
> echo '-------'
> done
0 files updated, 0 files merged, 2 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
0: Adding root node ()
-------
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
1: Adding a branch (a)
-------
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
-------
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
-------
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
-------
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
-------
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
-------
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
-------
7: Adding c branch (c)
-------
=======
$ for i in a b c z; do
> heads "$i"
> echo '-------'
> done
1: Adding a branch (a)
-------
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
-------
7: Adding c branch (c)
-------
abort: unknown revision 'z'!
-------
=======
$ heads 0 1 2 3 4 5 6 7
7: Adding c branch (c)
6: Merging b branch head 2 and b branch head 3 (b)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
0: Adding root node ()
Topological heads:
$ heads -t
7: Adding c branch (c)
3: Adding b branch head 1 (b)
1: Adding a branch (a)
$ cd ..
______________
"created new head" message tests
$ hg init newheadmsg
$ cd newheadmsg
Init: no msg
$ echo 1 > a
$ hg ci -Am "a0: Initial root"
adding a
$ echo 2 >> a
$ hg ci -m "a1 (HN)"
$ hg branch b
marked working directory as branch b
(branches are permanent and global, did you want a bookmark?)
$ echo 1 > b
$ hg ci -Am "b2: Initial root for branch b"
adding b
$ echo 2 >> b
$ hg ci -m "b3 (HN)"
Case NN: msg
$ hg up -q null
$ hg branch -f b
marked working directory as branch b
$ echo 1 > bb
$ hg ci -Am "b4 (NN): new topo root for branch b"
adding bb
created new head
Case HN: no msg
$ echo 2 >> bb
$ hg ci -m "b5 (HN)"
Case BN: msg
$ hg branch -f default
marked working directory as branch default
$ echo 1 > aa
$ hg ci -Am "a6 (BN): new branch root"
adding aa
created new head
Case CN: msg
$ hg up -q 4
$ echo 3 >> bbb
$ hg ci -Am "b7 (CN): regular new head"
adding bbb
created new head
Case BB: msg
$ hg up -q 4
$ hg merge -q 3
$ hg branch -f default
marked working directory as branch default
$ hg ci -m "a8 (BB): weird new branch root"
created new head
Case CB: msg
$ hg up -q 4
$ hg merge -q 1
$ hg ci -m "b9 (CB): new head from branch merge"
created new head
Case HB: no msg
$ hg up -q 7
$ hg merge -q 6
$ hg ci -m "b10 (HB): continuing head from branch merge"
Case CC: msg
$ hg up -q 4
$ hg merge -q 2
$ hg ci -m "b11 (CC): new head from merge"
created new head
Case CH: no msg
$ hg up -q 2
$ hg merge -q 10
$ hg ci -m "b12 (CH): continuing head from merge"
Case HH: no msg
$ hg merge -q 3
$ hg ci -m "b12 (HH): merging two heads"
$ cd ..