Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interval: add btree implementation of interval.Tree #8867

Merged
merged 3 commits into from
Jun 1, 2017

Conversation

yaojingguo
Copy link
Contributor

@yaojingguo yaojingguo commented Aug 27, 2016

Fixes #6465

All the benchmarking is done on a Thinkpad 440s laptop with a dual-core 2.10GHz Intel Core i7-4600U processor and 8 GB DDR3 memory. The laptop is running Ubuntu 14.04. The benchmark code is on https://github.com/yaojingguo/benchmark-interval-tree.

Augmentation Approaches

I have tried two approaches to augment a B-tree. One is issue-6465-opt branch. The other is rightmosts-guided-bst branch. The difference between these two approaches is that the latter uses a rightmost interval end array to guide the binary search over node.interfaces. In the following benchstat report, the first column is for issue-6465-opt branch. The second column is for rightmosts-guided-bst branch.

name                            old time/op  new time/op   delta
Insert-4                         949ns ± 0%  11337ns ± 0%  +1094.60%  (p=0.016 n=5+4)
FastInsert-4                     890ns ± 1%    966ns ± 0%     +8.51%  (p=0.016 n=5+4)
Delete-4                        1.10µs ± 1%  11.69µs ± 1%   +966.51%  (p=0.008 n=5+5)
Get-4                           3.68µs ± 1%   6.36µs ± 2%    +72.84%  (p=0.008 n=5+5)
RandomInsert-4                  3.05µs ± 2%   9.87µs ± 2%   +224.09%  (p=0.008 n=5+5)
RandomFastInsert-4              2.93µs ± 2%   3.01µs ± 2%     +2.65%  (p=0.032 n=5+5)
RandomDelete-4                  3.28µs ± 1%  13.56µs ± 2%   +313.00%  (p=0.008 n=5+5)
RandomGet-4                      945µs ± 4%   1791µs ± 1%    +89.53%  (p=0.008 n=5+5)
GetFrom1k-4                     3.78µs ± 5%   5.48µs ± 2%    +45.11%  (p=0.008 n=5+5)
GetFrom10k-4                    4.80µs ± 1%   7.18µs ± 0%    +49.67%  (p=0.008 n=5+5)
GetFrom100k-4                   3.70µs ± 1%   6.19µs ± 0%    +67.39%  (p=0.016 n=5+4)
GetFrom1000k-4                  3.96µs ± 1%   6.74µs ± 1%    +70.05%  (p=0.008 n=5+5)
InsertWithSmallTree-4           2.28µs ± 2%   3.44µs ± 1%    +50.90%  (p=0.008 n=5+5)
FastInsertWithSmallTree-4       2.27µs ± 1%   2.99µs ± 1%    +31.62%  (p=0.008 n=5+5)
InsertAndDeleteWithSmallTree-4  3.42µs ± 1%   4.56µs ± 1%    +33.48%  (p=0.008 n=5+5)
InsertAndGetWithSmallTree-4     5.59µs ± 2%   7.51µs ± 1%    +34.34%  (p=0.008 n=5+5)

The maintenance of the rightmost interval end array makes Insert and Delete methods slow. Its use also complicates the traversal of the interval tree. As a result, even the traversal is slower. If the array is updated incrementally instead of being rebuilt every time, Insert and Delete methods might run faster. But I can't figure out how to speed up the traversal. So I think that the latter is not a viable approach.

Optimization

issue-6465 branch is a basic implementation of the former approach. It has a slower Get method compared to LLRB based interval tree. Profiling shows that the function call of ToLeft slows it down. The removal of ToLeft function call results issue-6465-opt branch. Here is the benchstat report. The first column is for issue-6465 branch. The second column is for issue-6465-opt branch:

name                            old time/op  new time/op  delta
Insert-4                         949ns ± 1%   946ns ± 0%     ~     (p=0.238 n=5+5)
FastInsert-4                     887ns ± 0%   889ns ± 0%     ~     (p=0.246 n=5+5)
Delete-4                        1.10µs ± 0%  1.10µs ± 0%     ~     (p=0.286 n=5+5)
Get-4                           4.76µs ± 1%  3.73µs ± 2%  -21.67%  (p=0.008 n=5+5)
RandomInsert-4                  3.04µs ± 1%  2.99µs ± 1%   -1.68%  (p=0.016 n=5+4)
RandomFastInsert-4              2.92µs ± 2%  2.94µs ± 2%     ~     (p=0.651 n=5+5)
RandomDelete-4                  3.31µs ± 4%  3.30µs ± 5%     ~     (p=1.000 n=5+5)
RandomGet-4                     1.06ms ± 1%  0.96ms ± 4%   -9.20%  (p=0.008 n=5+5)
GetFrom1k-4                     4.32µs ± 5%  3.68µs ± 2%  -14.68%  (p=0.008 n=5+5)
GetFrom10k-4                    5.58µs ± 2%  4.81µs ± 1%  -13.73%  (p=0.008 n=5+5)
GetFrom100k-4                   4.69µs ± 0%  3.69µs ± 0%  -21.34%  (p=0.016 n=4+5)
GetFrom1000k-4                  5.04µs ± 0%  3.92µs ± 0%  -22.05%  (p=0.016 n=4+5)
InsertWithSmallTree-4           2.35µs ± 8%  2.30µs ± 1%     ~     (p=0.579 n=5+5)
FastInsertWithSmallTree-4       2.29µs ± 2%  2.27µs ± 1%     ~     (p=0.286 n=4+5)
InsertAndDeleteWithSmallTree-4  3.40µs ± 1%  3.41µs ± 0%     ~     (p=0.413 n=4+5)
InsertAndGetWithSmallTree-4     6.06µs ±10%  5.65µs ± 1%   -6.75%  (p=0.008 n=5+5)

B-Tree Degree Benchmark

The following report is produced by benchmarking B-tree degrees:

name \ time/op  degree_2     degree_4     degree_8     degree_16    degree_32    degree_64    degree_128   degree_256
Insert-4        2.40µs ± 0%  1.39µs ± 0%  1.12µs ± 0%  1.01µs ± 0%  0.95µs ± 0%  0.93µs ± 0%  0.89µs ± 1%   0.90µs ± 1%
FastInsert-4    2.17µs ± 3%  1.26µs ± 0%  1.03µs ± 1%  1.06µs ± 7%  0.89µs ± 0%  0.87µs ± 0%  0.83µs ± 0%   0.83µs ± 0%
Delete-4        2.43µs ± 2%  1.56µs ± 2%  1.27µs ± 0%  1.18µs ± 0%  1.11µs ± 0%  1.07µs ± 0%  1.06µs ± 0%   1.08µs ± 1%
Get-4           4.66µs ± 3%  3.58µs ± 4%  3.39µs ± 1%  3.40µs ± 2%  3.66µs ± 1%  5.15µs ± 1%  5.87µs ± 0%  10.08µs ± 0%

The following diagram puts the above report into a chart. Average means the average of the above four methods. When degree is 32, Average is minimum. If we assume that these four methods are used uniformly, degree 32 is a reasonable choice.

degree

Comparison with LLRB based Interval Tree

For all the reports in this section, the first column is for LLRB based interval tree. The second column is B-tree based interval tree.

name                            old time/op  new time/op  delta
Insert-4                        1.41µs ± 2%  1.00µs ± 6%  -28.99%   (p=0.000 n=8+10)
FastInsert-4                     949ns ±10%   969ns ±14%     ~     (p=0.305 n=10+10)
Delete-4                        3.15µs ± 2%  1.17µs ± 8%  -62.96%   (p=0.000 n=9+10)
Get-4                           4.21µs ± 2%  3.88µs ±12%   -7.83%   (p=0.008 n=9+10)
GetFrom1k-4                     3.73µs ± 2%  3.36µs ±10%   -9.96%   (p=0.000 n=9+10)
GetFrom10k-4                    4.64µs ± 3%  4.47µs ±20%     ~     (p=0.089 n=10+10)
GetFrom100k-4                   3.97µs ± 8%  3.81µs ±10%     ~     (p=0.183 n=10+10)
GetFrom1000k-4                  4.64µs ± 5%  4.16µs ±11%  -10.46%   (p=0.001 n=9+10)
InsertWithSmallTree-4           1.77µs ± 9%  2.06µs ± 9%  +16.65%  (p=0.000 n=10+10)
FastInsertWithSmallTree-4       1.70µs ± 8%  2.04µs ±10%  +20.25%  (p=0.000 n=10+10)
InsertAndDeleteWithSmallTree-4  2.42µs ± 6%  3.28µs ±11%  +35.08%   (p=0.000 n=9+10)
InsertAndGetWithSmallTree-4     5.05µs ± 2%  5.12µs ±11%     ~      (p=0.360 n=8+10)

The conclusion is that LLRB based interval tree is faster than B-tree based interval tree for small trees. But for large trees, B-tree based interval tree is faster, especially for Delete method.

The following reports are produced with the use of random byte slice with a random length. Report for random byte slice with a length between 1 and 16:

name                old time/op  new time/op  delta
RandomInsert-4      4.10µs ± 4%  2.52µs ± 3%  -38.48%  (p=0.000 n=10+10)
RandomFastInsert-4  2.96µs ± 4%  2.42µs ± 3%  -18.11%  (p=0.000 n=10+10)
RandomDelete-4      5.88µs ± 2%  2.58µs ± 2%  -56.05%    (p=0.000 n=9+9)
RandomGet-4         2.02ms ± 7%  1.04ms ± 2%  -48.45%   (p=0.000 n=10+9)

Report for random byte slice with a length between 1 and 32:

name                old time/op  new time/op  delta
RandomInsert-4      4.17µs ± 3%  2.77µs ± 4%  -33.64%  (p=0.000 n=10+10)
RandomFastInsert-4  2.95µs ± 6%  2.63µs ± 3%  -10.61%   (p=0.000 n=10+9)
RandomDelete-4      6.13µs ± 2%  2.75µs ± 2%  -55.16%   (p=0.000 n=10+9)
RandomGet-4         1.92ms ± 4%  1.17ms ± 6%  -38.89%   (p=0.000 n=10+9)

Report for random byte slice with a length between 1 and 64:

name                old time/op  new time/op  delta
RandomInsert-4      4.45µs ± 4%  2.82µs ±10%  -36.61%  (p=0.000 n=10+10)
RandomFastInsert-4  3.13µs ± 4%  2.74µs ± 7%  -12.41%  (p=0.000 n=10+10)
RandomDelete-4      6.28µs ± 3%  2.79µs ± 4%  -55.65%  (p=0.000 n=10+10)
RandomGet-4         1.94ms ± 6%  1.41ms ± 6%  -27.32%  (p=0.000 n=10+10)

Report for random byte slice with a length between 1 and 128:

name                old time/op  new time/op  delta
RandomInsert-4      4.81µs ± 6%  2.94µs ± 5%  -38.95%  (p=0.000 n=10+10)
RandomFastInsert-4  3.34µs ± 4%  2.76µs ± 5%  -17.24%   (p=0.000 n=9+10)
RandomDelete-4      6.78µs ± 3%  2.88µs ± 1%  -57.46%   (p=0.000 n=10+8)
RandomGet-4         1.95ms ± 7%  1.36ms ± 8%  -30.20%  (p=0.000 n=10+10)

Report for random byte slice with a length between 1 and 256:

name                old time/op  new time/op  delta
RandomInsert-4      4.78µs ± 6%  3.11µs ± 8%  -34.90%   (p=0.000 n=10+9)
RandomFastInsert-4  3.38µs ± 6%  2.89µs ±10%  -14.38%  (p=0.000 n=10+10)
RandomDelete-4      6.71µs ± 3%  3.02µs ± 3%  -54.94%  (p=0.000 n=10+10)
RandomGet-4         1.89ms ± 7%  1.19ms ±12%  -37.32%  (p=0.000 n=10+10)

Report for random byte slice with a length between 1 and 512:

name                old time/op  new time/op  delta
RandomInsert-4      4.18µs ± 4%  3.11µs ± 6%  -25.60%  (p=0.000 n=10+10)
RandomFastInsert-4  3.48µs ± 9%  3.03µs ± 8%  -13.07%  (p=0.000 n=10+10)
RandomDelete-4      6.70µs ± 3%  3.22µs ± 5%  -52.01%  (p=0.000 n=10+10)
RandomGet-4         1.83ms ± 2%  0.98ms ± 7%  -46.37%   (p=0.000 n=9+10)

Report for random byte slice with a length between 1 and 1024:

name                old time/op  new time/op  delta
RandomInsert-4      4.41µs ±11%  3.18µs ±12%  -28.01%  (p=0.000 n=10+10)
RandomFastInsert-4  3.52µs ± 5%  3.02µs ± 7%  -14.24%  (p=0.000 n=10+10)
RandomDelete-4      6.86µs ± 4%  3.43µs ± 5%  -50.03%  (p=0.000 n=10+10)
RandomGet-4         1.82ms ± 3%  0.98ms ± 3%  -46.35%  (p=0.000 n=10+10)

All the reports for random byte slice also indicate that B-tree based interval tree is faster than LLRB based interval tree.

Possible Optimizations

  1. Do a 256 way or 256^2 branching based on the first byte or the first two bytes.
  2. Determine common prefix length for node range start and end. Then sort.Search only needs to do byte slice comparison starting from the common prefix length.

@petermattis, could you please help me review this PR? If you think that issue-6465-opt branch is the right way to go. I will go proceed to dig into these optimizations.


This change is Reviewable

@petermattis
Copy link
Collaborator

@yaojingguo I'm not going to be able to take a look at this any time soon. @paperstreet, @dt: Can you lend a hand in reviewing this and deciding if the performance improvement is worth pursuing?

@dt dt self-assigned this Aug 29, 2016
@yaojingguo yaojingguo closed this Aug 29, 2016
@yaojingguo yaojingguo reopened this Aug 29, 2016
@yaojingguo
Copy link
Contributor Author

The CircleCi build fails with such a message:

acceptance: --- PASS: TestStatusServer (5.29s)

real    3m37.558s
user    0m5.149s
sys 0m0.815s
+ '[' -n /tmp/circle-junit.qQNLfw0 ']'
+ '[' -f /tmp/circle-artifacts.v9bEijv/test.log ']'
+ mkdir -p /tmp/circle-junit.qQNLfw0/test
+ build/builder.sh go2xunit --fail-on-race
error: bufio.Scanner: token too long
read unix @->/var/run/docker.sock: read: connection reset by peer

build/circle-test.sh returned exit code 1

@knz's suggestion is to rebuild. But after many retries, the build still fails. I have locally run make test successfully on my laptop.

@knz
Copy link
Contributor

knz commented Aug 30, 2016

I just started another run while disabling the CI cache. Let's see what that gives. Also with a different PR I eventually got success by rebasing to a newer develop tip.

@dt
Copy link
Member

dt commented Aug 31, 2016

Thanks @yaojingguo, this looks really exciting! Based on the gains we've seen in the past from optimizing these caches, these micro benchmark numbers seem worth pursuing.

Before going further on that though, at a high level, I think I'd be interested in keeping the current LLBR Tree implementation around and adding this as as a new, alternative and side-by-side implementation, with some common interface allowing picking between the two implementations at runtime via an env var (and maybe defaulting to the current LLBR for now).

Also, given the current stability focus, we probably want split that change up into two PRs: one to master (where any changes will need careful stability review) which just adds the common interface and the minimum changes for that, then the second, with the addition of the btree implementation, can go to develop, where we're more comfortable with non-stability-critical changes right now.

Back to the numbers: I ran some of the SQL level benchmarks for 100 and 1000 row operations (go test -run - -bench 00_C ./sql), to get a sense for how much this change impacts them:

$  benchstat bench.master bench.btree
name                                old time/op    new time/op    delta
Insert100_Cockroach-32                1.70ms ± 2%    1.78ms ± 2%   +5.21%  (p=0.000 n=10+10)
Insert1000_Cockroach-32               10.9ms ± 5%    11.6ms ± 3%   +6.85%  (p=0.000 n=10+10)
Update100_Cockroach-32                4.28ms ± 1%    4.63ms ± 4%   +8.21%  (p=0.000 n=10+10)
Update1000_Cockroach-32               31.5ms ± 2%    34.6ms ± 3%   +9.93%  (p=0.000 n=10+10)
Upsert100_Cockroach-32                6.54ms ± 2%    7.04ms ± 2%   +7.63%  (p=0.000 n=10+10)
Upsert1000_Cockroach-32               48.1ms ± 1%    51.7ms ± 2%   +7.43%   (p=0.000 n=8+10)
Delete100_Cockroach-32                2.23ms ± 3%    2.24ms ± 1%     ~     (p=0.631 n=10+10)
Delete1000_Cockroach-32               15.1ms ± 2%    15.4ms ± 3%   +1.44%  (p=0.015 n=10+10)
Scan100_Cockroach-32                   425µs ± 1%     433µs ± 5%     ~      (p=0.095 n=9+10)
Scan1000_Cockroach-32                 2.42ms ± 1%    2.44ms ± 1%   +0.94%  (p=0.015 n=10+10)
Scan10000_Cockroach-32                22.0ms ± 3%    22.0ms ± 1%     ~     (p=0.739 n=10+10)
Scan1000Limit100_Cockroach-32          449µs ± 3%     457µs ± 3%     ~     (p=0.063 n=10+10)
InterleavedSelect1000_Cockroach-32    94.2ms ± 2%    92.7ms ± 2%   -1.60%  (p=0.003 n=10+10)
TrackChoices100_Cockroach-32          59.2µs ± 2%    62.0µs ± 2%   +4.60%  (p=0.000 n=10+10)
TrackChoices1000_Cockroach-32         50.5µs ± 3%    54.0µs ± 4%   +7.09%  (p=0.000 n=10+10)
InsertDistinct100_Cockroach-32        1.48ms ± 2%    1.39ms ± 7%   -5.97%   (p=0.000 n=9+10)
WideTable100_Cockroach-32             13.8ms ± 4%    14.2ms ± 4%   +3.02%  (p=0.011 n=10+10)
WideTable1000_Cockroach-32             114ms ± 3%     118ms ± 2%   +3.82%  (p=0.000 n=10+10)

name                                old allocs/op  new allocs/op  delta
Insert100_Cockroach-32                 2.34k ± 0%     2.95k ± 0%  +26.17%   (p=0.000 n=9+10)
Insert1000_Cockroach-32                20.5k ± 0%     26.5k ± 0%  +29.68%   (p=0.000 n=10+9)
Update100_Cockroach-32                 3.75k ± 0%     4.94k ± 0%  +31.53%  (p=0.000 n=10+10)
Update1000_Cockroach-32                30.5k ± 0%     41.1k ± 0%  +34.74%  (p=0.000 n=10+10)
Upsert100_Cockroach-32                 5.97k ± 0%     7.00k ± 0%  +17.19%  (p=0.000 n=10+10)
Upsert1000_Cockroach-32                47.7k ± 0%     57.9k ± 0%  +21.33%  (p=0.000 n=10+10)
Delete100_Cockroach-32                 1.69k ± 0%     1.71k ± 0%   +1.30%  (p=0.000 n=10+10)
Delete1000_Cockroach-32                12.6k ± 0%     12.6k ± 0%   +0.18%   (p=0.000 n=10+9)
Scan100_Cockroach-32                     416 ± 0%       421 ± 0%   +1.20%  (p=0.000 n=10+10)
Scan1000_Cockroach-32                  2.29k ± 0%     2.30k ± 0%   +0.23%   (p=0.000 n=8+10)
Scan10000_Cockroach-32                 21.1k ± 0%     21.1k ± 0%   +0.05%  (p=0.000 n=10+10)
Scan1000Limit100_Cockroach-32            423 ± 0%       428 ± 0%   +1.18%  (p=0.000 n=10+10)
InterleavedSelect1000_Cockroach-32     2.96k ± 0%     2.97k ± 0%   +0.31%  (p=0.000 n=10+10)
TrackChoices100_Cockroach-32            79.0 ± 0%      97.0 ± 0%  +22.78%   (p=0.000 n=10+9)
TrackChoices1000_Cockroach-32           76.0 ± 0%      94.0 ± 0%  +23.68%  (p=0.000 n=10+10)
InsertDistinct100_Cockroach-32         2.73k ± 0%     2.99k ± 0%   +9.62%   (p=0.000 n=10+9)
WideTable100_Cockroach-32              24.0k ± 0%     25.7k ± 0%   +7.35%  (p=0.000 n=10+10)
WideTable1000_Cockroach-32              225k ± 0%      243k ± 0%   +7.88%   (p=0.000 n=10+9)

Looks like the new implementation is allocating somewhat more than the old one (which @nvanbenschoten had repeated combed over for any possible extra allocations) and I'm guessing the additional allocs explain the slightly lower performance. What's really promising though is that some of the alloc increases are much larger than the corresponding net time increase, making me optimistic that there are some real perf gains that are just hiding behind those allocs -- if we can track down the later, this could potentially speed those benchmarks up quite a bit.

@yaojingguo yaojingguo changed the base branch from master to develop September 2, 2016 07:59
@yaojingguo yaojingguo force-pushed the issue-6465-opt branch 2 times, most recently from c3e3f4c to efcdbb0 Compare September 11, 2016 10:30
@yaojingguo
Copy link
Contributor Author

yaojingguo commented Sep 11, 2016

The new implementation does not use a Tree interface shared by LLRB implementation and B-tree implementation because benchmarks show that such an indirection will cause more memory allocations. Instead, it choose LLRB implementation or B-tree implementation based on tags given at build time. The implementation defaults to LLRB. When btree tag is given, B-tree implementation will be used. The only difference between the LLRB implementation in develop branch and the LLRB implementation in this new commit is that Overlapper is a struct instead of a function in this commit. Compared with the B-tree implementation in the previous commit, the new B-tree implementation removes the use of FreeList, which reduces the allocations.

I have run the following benchmarks on a Linode 8192 machine (4 CPU cores and 8 G Memory).In all the following reports, the first column is for the LLRB implementation in the new commit. The second column is for the B-tree implementation in the new commit.

Micro Benchmarks

The benchmarks are defined in https://github.com/yaojingguo/benchmark-interval-tree. The following reports are produced by running benchstat on the result of executing b.sh with some irrelevant reports removed.

The report shows that there is no clear winner for tree size 4. But for all bigger tree sizes, B-tree is better than LLRB. The optimizations that I have mentioned in the previous comment are not worth of a try since they both prefer large tree sizes instead of small tree sizes.

name                                     old time/op    new time/op    delta
Insert4-4                                  1.18µs ±21%    1.33µs ± 6%   +12.62%        (p=0.004 n=10+10)
FastInsert4-4                              1.15µs ±10%    1.40µs ±17%   +22.07%        (p=0.000 n=10+10)
Delete4-4                                  2.19µs ± 3%    2.72µs ± 5%   +24.37%          (p=0.000 n=8+8)
Get4-4                                     3.61µs ± 8%    2.80µs ± 5%   -22.44%         (p=0.000 n=10+8)
Insert8-4                                  2.83µs ± 4%    2.55µs ± 6%    -9.88%         (p=0.000 n=10+9)
FastInsert8-4                              2.53µs ± 4%    2.50µs ±10%      ~           (p=0.382 n=10+10)
Delete8-4                                  5.29µs ± 7%    5.38µs ±11%      ~            (p=0.549 n=9+10)
Get8-4                                     13.5µs ±20%     8.5µs ± 7%   -37.24%        (p=0.000 n=10+10)
Insert100-4                                74.0µs ±14%    48.6µs ±12%   -34.34%        (p=0.000 n=10+10)
FastInsert100-4                            50.7µs ± 9%    44.4µs ± 8%   -12.43%         (p=0.000 n=9+10)
Delete100-4                                 150µs ± 9%     101µs ± 7%   -32.53%         (p=0.000 n=10+9)
Get100-4                                    489µs ±13%     286µs ± 8%   -41.58%        (p=0.000 n=10+10)
Insert1K-4                                 1.03ms ± 1%    0.69ms ±10%   -32.21%         (p=0.000 n=7+10)
FastInsert1K-4                              705µs ± 5%     643µs ± 9%    -8.82%         (p=0.000 n=9+10)
Delete1K-4                                 2.40ms ± 7%    1.40ms ± 3%   -41.50%         (p=0.000 n=10+9)
Get1K-4                                    5.81ms ± 6%    3.84ms ± 8%   -33.87%          (p=0.000 n=9+9)
Insert10K-4                                14.4ms ± 7%     9.9ms ±10%   -31.27%         (p=0.000 n=10+9)
FastInsert10K-4                            10.0ms ± 8%     9.7ms ±12%      ~           (p=0.436 n=10+10)
Delete10K-4                                36.5ms ± 6%    20.4ms ± 9%   -44.05%        (p=0.000 n=10+10)
Get10K-4                                   69.5ms ±10%    47.0ms ± 7%   -32.46%          (p=0.000 n=9+9)
Insert100K-4                                162ms ± 4%     108ms ± 5%   -33.07%         (p=0.000 n=10+9)
FastInsert100K-4                            110ms ± 3%     106ms ± 4%    -4.20%         (p=0.002 n=10+9)
Delete100K-4                                459ms ± 2%     242ms ± 5%   -47.36%         (p=0.000 n=9+10)
Get100K-4                                   691ms ± 4%     516ms ± 6%   -25.23%        (p=0.000 n=10+10)
Insert1M-4                                  1.90s ± 2%     1.29s ± 4%   -32.50%          (p=0.000 n=9+9)
FastInsert1M-4                              1.20s ± 2%     1.20s ± 3%      ~            (p=0.573 n=10+8)
Delete1M-4                                  5.64s ± 9%     2.82s ± 9%   -49.99%        (p=0.000 n=10+10)
Get1M-4                                     7.21s ± 3%     5.82s ± 4%   -19.27%        (p=0.000 n=10+10)
RandomInsert4-4                            1.77µs ±12%    1.52µs ± 5%   -14.24%         (p=0.000 n=10+8)
RandomFastInsert4-4                        1.72µs ±17%    1.50µs ± 9%   -12.52%        (p=0.003 n=10+10)
RandomDelete4-4                            2.51µs ±16%    2.75µs ± 8%    +9.57%        (p=0.007 n=10+10)
RandomGet4-4                               3.21µs ±29%    2.77µs ±39%      ~           (p=0.101 n=10+10)
RandomInsert8-4                            4.08µs ±13%    2.82µs ±14%   -30.99%        (p=0.000 n=10+10)
RandomFastInsert8-4                        3.73µs ±10%    2.82µs ± 4%   -24.39%         (p=0.000 n=10+8)
RandomDelete8-4                            6.58µs ±19%    5.76µs ±11%   -12.48%        (p=0.003 n=10+10)
RandomGet8-4                               12.8µs ±13%     8.9µs ±18%   -30.18%          (p=0.000 n=9+9)
RandomInsert100-4                          94.6µs ± 9%    63.5µs ± 8%   -32.92%         (p=0.000 n=9+10)
RandomFastInsert100-4                      69.3µs ± 7%    60.6µs ± 7%   -12.60%        (p=0.000 n=10+10)
RandomDelete100-4                           194µs ± 7%     120µs ± 9%   -37.97%        (p=0.000 n=10+10)
RandomGet100-4                             2.12ms ±10%    1.19ms ±13%   -43.66%        (p=0.000 n=10+10)
RandomInsert1K-4                           1.64ms ±10%    1.01ms ± 6%   -38.03%         (p=0.000 n=9+10)
RandomFastInsert1K-4                       1.20ms ± 6%    0.96ms ± 4%   -19.86%         (p=0.000 n=8+10)
RandomDelete1K-4                           3.50ms ±10%    2.03ms ± 5%   -41.86%        (p=0.000 n=10+10)
RandomGet1K-4                               246ms ±11%     134ms ± 7%   -45.54%         (p=0.000 n=10+9)
RandomInsert10K-4                          26.7ms ± 9%    16.0ms ± 5%   -40.24%         (p=0.000 n=10+8)
RandomFastInsert10K-4                      18.7ms ± 6%    15.7ms ±11%   -15.95%        (p=0.000 n=10+10)
RandomDelete10K-4                          60.4ms ± 7%    36.3ms ± 9%   -39.88%        (p=0.000 n=10+10)
RandomGet10K-4                              31.3s ±16%     14.6s ± 8%   -53.30%        (p=0.000 n=10+10)
RandomInsert100K-4                          578ms ±12%     398ms ±24%   -31.17%        (p=0.000 n=10+10)
RandomFastInsert100K-4                      441ms ±22%     378ms ±16%   -14.25%        (p=0.011 n=10+10)
RandomDelete100K-4                          1.35s ± 8%     0.79s ±14%   -41.33%        (p=0.000 n=10+10)
RandomInsert1M-4                            12.7s ±11%      8.4s ±16%   -34.28%        (p=0.000 n=10+10)
RandomFastInsert1M-4                        8.83s ±10%     7.98s ±21%    -9.64%         (p=0.043 n=9+10)
RandomDelete1M-4                            27.4s ± 8%     16.7s ±18%   -39.09%        (p=0.000 n=10+10)

name                                     old alloc/op   new alloc/op   delta
Insert4-4                                    384B ± 0%      272B ± 0%   -29.17%        (p=0.000 n=10+10)
FastInsert4-4                                384B ± 0%      272B ± 0%   -29.17%        (p=0.000 n=10+10)
Delete4-4                                    384B ± 0%      272B ± 0%   -29.17%        (p=0.000 n=10+10)
Get4-4                                       448B ± 0%      448B ± 0%      ~     (all samples are equal)
Insert8-4                                    768B ± 0%      400B ± 0%   -47.92%        (p=0.000 n=10+10)
FastInsert8-4                                768B ± 0%      400B ± 0%   -47.92%        (p=0.000 n=10+10)
Delete8-4                                    768B ± 0%      400B ± 0%   -47.92%        (p=0.000 n=10+10)
Get8-4                                     1.92kB ± 0%    1.92kB ± 0%      ~     (all samples are equal)
Insert100-4                                9.60kB ± 0%    5.70kB ± 0%   -40.67%        (p=0.000 n=10+10)
FastInsert100-4                            9.60kB ± 0%    5.70kB ± 0%   -40.67%        (p=0.000 n=10+10)
Delete100-4                                9.60kB ± 0%    5.70kB ± 0%   -40.67%        (p=0.000 n=10+10)
Get100-4                                   94.7kB ± 0%    94.7kB ± 0%      ~     (all samples are equal)
Insert1K-4                                 96.0kB ± 0%    53.2kB ± 0%   -44.53%        (p=0.000 n=10+10)
FastInsert1K-4                             96.0kB ± 0%    53.2kB ± 0%   -44.53%        (p=0.000 n=10+10)
Delete1K-4                                 96.0kB ± 0%    53.2kB ± 0%   -44.53%        (p=0.000 n=10+10)
Get1K-4                                    1.00MB ± 0%    1.00MB ± 0%      ~     (all samples are equal)
Insert10K-4                                 960kB ± 0%     538kB ± 0%   -43.98%        (p=0.000 n=10+10)
FastInsert10K-4                             960kB ± 0%     538kB ± 0%   -43.98%        (p=0.000 n=10+10)
Delete10K-4                                 960kB ± 0%     538kB ± 0%   -43.98%        (p=0.000 n=10+10)
Get10K-4                                   10.1MB ± 0%    10.1MB ± 0%      ~     (all samples are equal)
Insert100K-4                               9.60MB ± 0%    5.39MB ± 0%   -43.81%        (p=0.000 n=10+10)
FastInsert100K-4                           9.60MB ± 0%    5.39MB ± 0%   -43.81%        (p=0.000 n=10+10)
Delete100K-4                               9.60MB ± 0%    5.39MB ± 0%   -43.81%        (p=0.000 n=10+10)
Get100K-4                                   101MB ± 0%     101MB ± 0%      ~     (all samples are equal)
Insert1M-4                                 96.0MB ± 0%    53.9MB ± 0%   -43.82%        (p=0.000 n=10+10)
FastInsert1M-4                             96.0MB ± 0%    53.9MB ± 0%   -43.82%        (p=0.000 n=10+10)
Delete1M-4                                 96.0MB ± 0%    53.9MB ± 0%   -43.82%        (p=0.000 n=10+10)
Get1M-4                                    1.01GB ± 0%    1.01GB ± 0%      ~     (all samples are equal)
RandomInsert4-4                              384B ± 0%      272B ± 0%   -29.17%        (p=0.000 n=10+10)
RandomFastInsert4-4                          384B ± 0%      272B ± 0%   -29.17%        (p=0.000 n=10+10)
RandomDelete4-4                              384B ± 0%      272B ± 0%   -29.17%        (p=0.000 n=10+10)
RandomGet4-4                                 323B ±60%      390B ±67%      ~           (p=0.117 n=10+10)
RandomInsert8-4                              768B ± 0%      400B ± 0%   -47.92%        (p=0.000 n=10+10)
RandomFastInsert8-4                          768B ± 0%      400B ± 0%   -47.92%        (p=0.000 n=10+10)
RandomDelete8-4                              768B ± 0%      400B ± 0%   -47.92%        (p=0.000 n=10+10)
RandomGet8-4                               1.60kB ±16%    1.65kB ±38%      ~            (p=0.409 n=9+10)
RandomInsert100-4                          9.60kB ± 0%    3.98kB ± 0%   -58.50%        (p=0.000 n=10+10)
RandomFastInsert100-4                      9.60kB ± 0%    3.98kB ± 0%   -58.50%        (p=0.000 n=10+10)
RandomDelete100-4                          9.60kB ± 0%    3.98kB ± 0%   -58.50%        (p=0.000 n=10+10)
RandomGet100-4                              309kB ±10%     319kB ±12%      ~           (p=0.342 n=10+10)
RandomInsert1K-4                           96.0kB ± 0%    39.3kB ±10%   -59.04%        (p=0.000 n=10+10)
RandomFastInsert1K-4                       96.0kB ± 0%    39.2kB ± 7%   -59.21%        (p=0.000 n=10+10)
RandomDelete1K-4                           96.0kB ± 0%    39.8kB ± 6%   -58.58%         (p=0.000 n=10+9)
RandomGet1K-4                              28.6MB ± 3%    28.5MB ± 3%      ~           (p=0.986 n=10+10)
RandomInsert10K-4                           960kB ± 0%     378kB ± 3%   -60.66%        (p=0.000 n=10+10)
RandomFastInsert10K-4                       960kB ± 0%     376kB ± 4%   -60.79%        (p=0.000 n=10+10)
RandomDelete10K-4                           960kB ± 0%     382kB ± 5%   -60.16%        (p=0.000 n=10+10)
RandomGet10K-4                             4.65GB ± 1%    4.64GB ± 1%      ~           (p=0.529 n=10+10)
RandomInsert100K-4                         9.60MB ± 0%    3.79MB ± 1%   -60.47%        (p=0.000 n=10+10)
RandomFastInsert100K-4                     9.60MB ± 0%    3.81MB ± 1%   -60.36%        (p=0.000 n=10+10)
RandomDelete100K-4                         9.60MB ± 0%    3.85MB ± 1%   -59.92%        (p=0.000 n=10+10)
RandomInsert1M-4                           96.0MB ± 0%    37.9MB ± 0%   -60.51%        (p=0.000 n=10+10)
RandomFastInsert1M-4                       96.0MB ± 0%    37.9MB ± 0%   -60.52%        (p=0.000 n=10+10)
RandomDelete1M-4                           96.0MB ± 0%    38.3MB ± 0%   -60.11%        (p=0.000 n=10+10)

name                                     old allocs/op  new allocs/op  delta
Insert4-4                                    4.00 ± 0%      5.00 ± 0%   +25.00%        (p=0.000 n=10+10)
FastInsert4-4                                4.00 ± 0%      5.00 ± 0%   +25.00%        (p=0.000 n=10+10)
Delete4-4                                    4.00 ± 0%      5.00 ± 0%   +25.00%        (p=0.000 n=10+10)
Get4-4                                       12.0 ± 0%      12.0 ± 0%      ~     (all samples are equal)
Insert8-4                                    8.00 ± 0%      6.00 ± 0%   -25.00%        (p=0.000 n=10+10)
FastInsert8-4                                8.00 ± 0%      6.00 ± 0%   -25.00%        (p=0.000 n=10+10)
Delete8-4                                    8.00 ± 0%      6.00 ± 0%   -25.00%        (p=0.000 n=10+10)
Get8-4                                       32.0 ± 0%      32.0 ± 0%      ~     (all samples are equal)
Insert100-4                                   100 ± 0%        20 ± 0%   -80.00%        (p=0.000 n=10+10)
FastInsert100-4                               100 ± 0%        20 ± 0%   -80.00%        (p=0.000 n=10+10)
Delete100-4                                   100 ± 0%        20 ± 0%   -80.00%        (p=0.000 n=10+10)
Get100-4                                      588 ± 0%       588 ± 0%      ~     (all samples are equal)
Insert1K-4                                  1.00k ± 0%     0.11k ± 0%   -88.90%        (p=0.000 n=10+10)
FastInsert1K-4                              1.00k ± 0%     0.11k ± 0%   -88.90%        (p=0.000 n=10+10)
Delete1K-4                                  1.00k ± 0%     0.11k ± 0%   -88.90%        (p=0.000 n=10+10)
Get1K-4                                     5.99k ± 0%     5.99k ± 0%      ~     (all samples are equal)
Insert10K-4                                 10.0k ± 0%      1.0k ± 0%   -89.95%        (p=0.000 n=10+10)
FastInsert10K-4                             10.0k ± 0%      1.0k ± 0%   -89.95%        (p=0.000 n=10+10)
Delete10K-4                                 10.0k ± 0%      1.0k ± 0%   -89.95%        (p=0.000 n=10+10)
Get10K-4                                    60.0k ± 0%     60.0k ± 0%      ~     (all samples are equal)
Insert100K-4                                 100k ± 0%       10k ± 0%   -90.10%        (p=0.000 n=10+10)
FastInsert100K-4                             100k ± 0%       10k ± 0%   -90.10%        (p=0.000 n=10+10)
Delete100K-4                                 100k ± 0%       10k ± 0%   -90.10%        (p=0.000 n=10+10)
Get100K-4                                    600k ± 0%      600k ± 0%      ~     (all samples are equal)
Insert1M-4                                  1.00M ± 0%     0.10M ± 0%   -90.12%        (p=0.000 n=10+10)
FastInsert1M-4                              1.00M ± 0%     0.10M ± 0%   -90.12%        (p=0.000 n=10+10)
Delete1M-4                                  1.00M ± 0%     0.10M ± 0%   -90.12%        (p=0.000 n=10+10)
Get1M-4                                     6.00M ± 0%     6.00M ± 0%      ~     (all samples are equal)
RandomInsert4-4                              4.00 ± 0%      5.00 ± 0%   +25.00%        (p=0.000 n=10+10)
RandomFastInsert4-4                          4.00 ± 0%      5.00 ± 0%   +25.00%        (p=0.000 n=10+10)
RandomDelete4-4                              4.00 ± 0%      5.00 ± 0%   +25.00%        (p=0.000 n=10+10)
RandomGet4-4                                 9.70 ±38%     11.56 ±13%   +19.13%         (p=0.018 n=10+9)
RandomInsert8-4                              8.00 ± 0%      6.00 ± 0%   -25.00%        (p=0.000 n=10+10)
RandomFastInsert8-4                          8.00 ± 0%      6.00 ± 0%   -25.00%        (p=0.000 n=10+10)
RandomDelete8-4                              8.00 ± 0%      6.00 ± 0%   -25.00%        (p=0.000 n=10+10)
RandomGet8-4                                 29.1 ± 7%      30.1 ±17%      ~             (p=0.169 n=9+9)
RandomInsert100-4                             100 ± 0%        15 ± 0%   -85.00%        (p=0.000 n=10+10)
RandomFastInsert100-4                         100 ± 0%        15 ± 0%   -85.00%        (p=0.000 n=10+10)
RandomDelete100-4                             100 ± 0%        15 ± 0%   -85.00%        (p=0.000 n=10+10)
RandomGet100-4                                747 ± 3%       753 ± 3%      ~           (p=0.270 n=10+10)
RandomInsert1K-4                            1.00k ± 0%     0.09k ± 8%   -91.40%        (p=0.000 n=10+10)
RandomFastInsert1K-4                        1.00k ± 0%     0.09k ± 6%   -91.43%        (p=0.000 n=10+10)
RandomDelete1K-4                            1.00k ± 0%     0.09k ± 4%   -91.34%         (p=0.000 n=10+9)
RandomGet1K-4                               10.7k ± 1%     10.7k ± 0%      ~           (p=1.000 n=10+10)
RandomInsert10K-4                           10.0k ± 0%      0.7k ± 3%   -92.92%         (p=0.000 n=10+9)
RandomFastInsert10K-4                       10.0k ± 0%      0.7k ± 5%   -92.91%        (p=0.000 n=10+10)
RandomDelete10K-4                           10.0k ± 0%      0.7k ± 4%   -92.83%        (p=0.000 n=10+10)
RandomGet10K-4                               177k ± 0%      177k ± 0%      ~           (p=0.393 n=10+10)
RandomInsert100K-4                           100k ± 0%        7k ± 1%   -93.00%        (p=0.000 n=10+10)
RandomFastInsert100K-4                       100k ± 0%        7k ± 1%   -92.98%        (p=0.000 n=10+10)
RandomDelete100K-4                           100k ± 0%        7k ± 1%   -92.94%        (p=0.000 n=10+10)
RandomInsert1M-4                            1.00M ± 0%     0.07M ± 0%   -93.03%        (p=0.000 n=10+10)
RandomFastInsert1M-4                        1.00M ± 0%     0.07M ± 0%   -93.04%         (p=0.000 n=10+9)
RandomDelete1M-4                            1.00M ± 0%     0.07M ± 0%   -92.99%        (p=0.000 n=10+10)

-bench 00_C

The report shows that B-tree is slightly better than LLRB. This report is consistent with the agove micro benchmarks because these tests use small tree sizes. For example, the maximum tree used by Insert100_Cockroach has a size about 50.

name                               old time/op    new time/op    delta
Insert100_Cockroach-4                2.64ms ±33%    2.65ms ±23%     ~           (p=0.739 n=10+10)
Insert1000_Cockroach-4               16.6ms ±11%    16.7ms ± 7%     ~            (p=0.905 n=10+9)
Update100_Cockroach-4                6.46ms ±19%    6.60ms ±16%     ~           (p=0.529 n=10+10)
Update1000_Cockroach-4               46.4ms ±11%    47.3ms ±10%     ~           (p=0.631 n=10+10)
Upsert100_Cockroach-4                8.49ms ± 8%    8.86ms ±12%     ~            (p=0.447 n=9+10)
Upsert1000_Cockroach-4               53.9ms ± 6%    57.3ms ± 8%   +6.28%         (p=0.010 n=9+10)
Delete100_Cockroach-4                3.00ms ±10%    2.94ms ± 6%     ~            (p=0.400 n=10+9)
Delete1000_Cockroach-4               22.3ms ± 9%    21.4ms ± 8%     ~             (p=0.136 n=9+9)
Scan100_Cockroach-4                   630µs ±11%     646µs ±12%     ~           (p=0.481 n=10+10)
Scan1000_Cockroach-4                 3.45ms ±17%    3.34ms ±11%     ~            (p=0.400 n=9+10)
Scan10000_Cockroach-4                34.3ms ±23%    31.6ms ± 1%     ~            (p=0.417 n=10+7)
Scan1000Limit100_Cockroach-4          713µs ±24%     646µs ±13%     ~            (p=0.053 n=10+9)
InterleavedSelect1000_Cockroach-4     116ms ±10%     113ms ±17%     ~           (p=0.353 n=10+10)
TrackChoices100_Cockroach-4          98.3µs ±13%    96.8µs ±28%     ~            (p=0.573 n=8+10)
TrackChoices1000_Cockroach-4         79.1µs ±10%    75.6µs ± 7%     ~            (p=0.095 n=9+10)
InsertDistinct100_Cockroach-4        3.04ms ±22%    2.62ms ±32%  -13.75%         (p=0.022 n=10+9)
WideTable100_Cockroach-4             21.3ms ±11%    21.4ms ±23%     ~             (p=0.730 n=9+9)
WideTable1000_Cockroach-4             165ms ± 8%     168ms ±23%     ~            (p=0.497 n=9+10)

name                               old alloc/op   new alloc/op   delta
Insert100_Cockroach-4                 530kB ± 0%     529kB ± 0%     ~             (p=0.297 n=9+9)
Insert1000_Cockroach-4               4.59MB ± 0%    4.59MB ± 0%     ~             (p=0.200 n=8+9)
Update100_Cockroach-4                 849kB ± 0%     848kB ± 0%   -0.19%         (p=0.000 n=10+9)
Update1000_Cockroach-4               7.03MB ± 0%    7.01MB ± 0%   -0.32%          (p=0.000 n=8+9)
Upsert100_Cockroach-4                 938kB ± 0%     935kB ± 0%   -0.30%        (p=0.000 n=10+10)
Upsert1000_Cockroach-4               7.81MB ± 1%    7.82MB ± 1%     ~           (p=0.912 n=10+10)
Delete100_Cockroach-4                 166kB ± 2%     165kB ± 1%     ~           (p=0.143 n=10+10)
Delete1000_Cockroach-4               1.17MB ± 0%    1.17MB ± 0%     ~             (p=0.606 n=8+9)
Scan100_Cockroach-4                  53.9kB ± 0%    53.8kB ± 0%   -0.19%          (p=0.000 n=8+9)
Scan1000_Cockroach-4                  321kB ± 0%     321kB ± 0%   -0.03%         (p=0.000 n=9+10)
Scan10000_Cockroach-4                5.53MB ± 0%    5.53MB ± 0%   -0.00%        (p=0.007 n=10+10)
Scan1000Limit100_Cockroach-4         54.3kB ± 0%    54.3kB ± 0%   -0.18%        (p=0.000 n=10+10)
InterleavedSelect1000_Cockroach-4     792kB ± 0%     792kB ± 0%   -0.02%          (p=0.001 n=8+8)
TrackChoices100_Cockroach-4          19.1kB ± 1%    19.1kB ± 0%   +0.45%         (p=0.036 n=10+8)
TrackChoices1000_Cockroach-4         20.5kB ± 0%    20.5kB ± 0%     ~            (p=0.986 n=10+9)
InsertDistinct100_Cockroach-4         458kB ± 9%     432kB ± 1%   -5.75%         (p=0.002 n=10+8)
WideTable100_Cockroach-4             2.61MB ± 0%    2.61MB ± 0%     ~           (p=0.075 n=10+10)
WideTable1000_Cockroach-4            23.5MB ± 0%    23.5MB ± 0%   -0.16%          (p=0.000 n=9+9)

name                               old allocs/op  new allocs/op  delta
Insert100_Cockroach-4                 2.49k ± 0%     2.49k ± 0%     ~            (p=0.185 n=9+10)
Insert1000_Cockroach-4                21.5k ± 0%     21.5k ± 0%     ~             (p=0.055 n=8+9)
Update100_Cockroach-4                 3.75k ± 0%     3.71k ± 0%   -0.87%         (p=0.000 n=10+9)
Update1000_Cockroach-4                29.7k ± 0%     29.4k ± 0%   -1.01%          (p=0.000 n=8+9)
Upsert100_Cockroach-4                 5.95k ± 0%     5.90k ± 0%   -0.85%         (p=0.000 n=9+10)
Upsert1000_Cockroach-4                45.1k ± 0%     44.6k ± 0%   -1.12%         (p=0.000 n=9+10)
Delete100_Cockroach-4                 1.74k ± 0%     1.74k ± 0%   -0.21%        (p=0.045 n=10+10)
Delete1000_Cockroach-4                12.6k ± 0%     12.6k ± 0%   -0.02%         (p=0.000 n=9+10)
Scan100_Cockroach-4                     466 ± 0%       465 ± 0%   -0.21%         (p=0.002 n=8+10)
Scan1000_Cockroach-4                  2.35k ± 0%     2.35k ± 0%   -0.04%         (p=0.000 n=10+9)
Scan10000_Cockroach-4                 21.1k ± 0%     21.1k ± 0%     ~             (p=0.131 n=9+8)
Scan1000Limit100_Cockroach-4            474 ± 0%       473 ± 0%   -0.21%        (p=0.000 n=10+10)
InterleavedSelect1000_Cockroach-4     3.03k ± 0%     3.03k ± 0%   -0.06%        (p=0.000 n=10+10)
TrackChoices100_Cockroach-4            83.0 ± 0%      83.0 ± 0%     ~     (all samples are equal)
TrackChoices1000_Cockroach-4           79.0 ± 0%      79.0 ± 0%     ~     (all samples are equal)
InsertDistinct100_Cockroach-4         4.09k ±17%     3.61k ± 2%  -11.84%         (p=0.000 n=10+8)
WideTable100_Cockroach-4              24.4k ± 0%     24.3k ± 0%   -0.42%         (p=0.000 n=10+9)
WideTable1000_Cockroach-4              227k ± 0%      226k ± 0%   -0.39%         (p=0.000 n=10+9)

@dt, could you please help me review this new commit? If the performance characteristics for the new implementation is fine, I will proceed to do more code cleanups and work out the PR for the master branch.

@dt dt assigned nvanbenschoten and unassigned petermattis Sep 13, 2016
@nvanbenschoten
Copy link
Member

I'm going to need a few more passes over this, as well as some time looking at cpu and memory profiles to see if we can get any easy perf wins, but so far it's looking good. Thanks for the help @yaojingguo!


Reviewed 1 of 9 files at r1, 8 of 11 files at r2.
Review status: 9 of 11 files reviewed at latest revision, 23 unresolved discussions, some commit checks failed.


Makefile, line 103 at r2 (raw file):

.PHONY: test
test:
  $(GO) test -v $(GOFLAGS) -tags '$(TAGS)' -i $(PKG)

I think @tamird would like this to be pulled into a separate commit.


util/interval/btree_based_interval.go, line 31 at r2 (raw file):

const (
  // DefaultBTreeDegree is the default B-tree degree. A benchmarks shows the interval tree perform

s/perform/performs/


util/interval/btree_based_interval.go, line 47 at r2 (raw file):

// New creates a new interval tree with the given overlapper function and the default B-tree degree.
func NewTree(overlapper Overlapper) Tree {
  return Tree{

return NewTreeWithDegree(overlapper, DefaultBTreeDegree)


util/interval/btree_based_interval.go, line 54 at r2 (raw file):

// NewWithDegree creates a new interval tree with the given overlapper function and the given
// degree. A degree less than 2 will result undefined behavior.

sresult/result in. Also, let's assert this condition in the constructor.


util/interval/btree_based_interval.go, line 58 at r2 (raw file):

// NewWithDegree(overlapper, 2), for example, will create a 2-3-4 tree (each node contains 1-3
// Interfaces and 2-4 children).
func NewTreeWithDegree(overlapper Overlapper, degree int) Tree {

I've usually seen this referred to as the "minimum degree" of the b-tree.


util/interval/btree_based_interval.go, line 71 at r2 (raw file):

// forward.
func (s *interfaces) insertAt(index int, e Interface) {
  oldLen := len(*s)

huh, this is slightly different than https://github.com/google/btree/blob/9cda4e30bb3bdd4f7e8ae79f795c0aeaf2b2efc3/btree.go#L127. Is theirs just incorrect?


util/interval/btree_based_interval.go, line 102 at r2 (raw file):

// index.
func (s interfaces) find(e Interface) (index int, found bool) {
  i := sort.Search(len(s), func(i int) bool {

I fear that this allocates (at least once) on each call...


util/interval/btree_based_interval.go, line 117 at r2 (raw file):

// forward.
func (s *children) insertAt(index int, n *node) {
  *s = append(*s, nil)

Should this be the same as interfaces.insertAt (with respect to your change)?


util/interval/btree_based_interval.go, line 151 at r2 (raw file):

  // Range is the node range which covers all the ranges in the subtree rooted at the node.
  // Range.Start is the leftmost position. Range.End is the rightmost position. Here we follow the
  // approach employed by https://github.com/biogo/store/tree/master/interval since it make it easy

long line.


util/interval/btree_based_interval.go, line 152 at r2 (raw file):

  // Range.Start is the leftmost position. Range.End is the rightmost position. Here we follow the
  // approach employed by https://github.com/biogo/store/tree/master/interval since it make it easy
  // to analyze the traversal of intervals which overlaps with a given interval. CRLS only uses

CRLS?


util/interval/btree_based_interval.go, line 155 at r2 (raw file):

  // Range.End.
  Range      Range
  interfaces interfaces

I don't think I like calling the field or the type interfaces. It sounds more clear to me in the context of a B-Tree to call them items (with a corresponding maxItems method and such).


util/interval/btree_based_interval.go, line 179 at r2 (raw file):

// +-----------+     +-----------+
//
func (n *node) split(i int, fast bool) (Interface, *node) {

The fast parameter should be mentioned in the comment.


util/interval/btree_based_interval.go, line 182 at r2 (raw file):

  e := n.interfaces[i]
  second := n.t.newNode()
  second.interfaces = make(interfaces, n.t.minInterfaces())

It seems like we always have some variation of setting .interfaces and .children


util/interval/btree_based_interval.go, line 232 at r2 (raw file):

      n.interfaces.insertAt(i, e)
      out = nil
      if !fast {

It would be a lot easier to review this if the fast optimization was split into a separate commit. Is that possible at this point?


util/interval/btree_based_interval.go, line 392 at r2 (raw file):

}

// benchmarks show that if Comparable.Compare is invoked directly instead of through an indirection

lets pull this method above the two methods it calls.


util/interval/btree_based_interval.go, line 437 at r2 (raw file):

  done = n.children[cLen-1].do(fn)
  return
}

Why was toRemove defined at the top instead of here like in the referenced code?


util/interval/btree_based_interval.go, line 440 at r2 (raw file):

// remove removes an interface from the subtree rooted at this node.
func (n *node) remove(e Interface, minInterfaces int, typ toRemove, fast bool) (out Interface, shrunk bool) {

long line, see style guidelines for function signatures.


util/interval/btree_based_interval.go, line 558 at r2 (raw file):

// whether we're in case 1 or 2), we'll have enough Interfaces and can guarantee
// that we hit case A.
func (n *node) growChildAndRemove(i int, e Interface, minInterfaces int, typ toRemove, fast bool) (out Interface, shrunk bool) {

long line


util/interval/btree_based_interval_test.go, line 31 at r2 (raw file):

)

var degree = flag.Int("degree", 32, "B-Tree degree")

Lets improve this flag name. Maybe btree_degree


util/interval/btree_based_interval_test.go, line 50 at r2 (raw file):

}

func (interfaces interfaces) Len() int {

Why not move these sorting methods next to the types themselves?


util/interval/btree_based_interval_test.go, line 349 at r2 (raw file):

}

func intervals() interfaces {

s/intervals/makeIntervals/


util/interval/btree_based_interval_test.go, line 491 at r2 (raw file):

  checkWithLen(t, &tree, no)
  checkFastDelete(t, &tree, ivs, 100)
}

Do we need other tests from https://github.com/google/btree/blob/9cda4e30bb3bdd4f7e8ae79f795c0aeaf2b2efc3/btree_test.go?


util/interval/llrb_based_interval.go, line 5 at r2 (raw file):

// license that can be found in the LICENSE file.

// +build !btree

This is all directly copied from interval.go, right? There were no changes?


Comments from Reviewable

@yaojingguo yaojingguo force-pushed the issue-6465-opt branch 3 times, most recently from 2b3ba21 to 17673b0 Compare September 25, 2016 06:17
@yaojingguo
Copy link
Contributor Author

Makefile, line 103 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

I think @tamird would like this to be pulled into a separate commit.

Open PR https://github.com//pull/9421. And the PR is merged into master branch and cherry-picked into develop branch.

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

util/interval/btree_based_interval.go, line 31 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

s/perform/performs/

Done

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

util/interval/btree_based_interval.go, line 47 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

return NewTreeWithDegree(overlapper, DefaultBTreeDegree)

Done. This incurs a function invocation and is related to performance. See my comment about new benchstat report.

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

util/interval/btree_based_interval.go, line 54 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

sresult/result in. Also, let's assert this condition in the constructor.

Change `result undefined behavior` to `cause a panic`. This change adds a validity check for `MinimumDegree` and is related to performance. See my comment about new benchstat report.

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

util/interval/btree_based_interval.go, line 58 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

I've usually seen this referred to as the "minimum degree" of the b-tree.

Yes, you are right. I have adopted this term from https://github.com/google/btree. But both CLRS and https://en.wikipedia.org/wiki/B-tree use the term `minimum degree`. Let's use it. `s/Degree/MinimumDegree/` is done.

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

util/interval/btree_based_interval.go, line 71 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

huh, this is slightly different than https://github.com/google/btree/blob/9cda4e30bb3bdd4f7e8ae79f795c0aeaf2b2efc3/btree.go#L127. Is theirs just incorrect?

Only if `index` is less that `s`'s original length, `copy` invocation is needed. Theirs is also correct. But `copy` does nothing if `index` is equal to `s`'s original length. I think that our version has a clear meaning. And if `cost(save oldLen) < probability(index is equal to oldLen) * cost(copy invocation)`, our version might be a little faster.

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

util/interval/btree_based_interval.go, line 102 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

I fear that this allocates (at least once) on each call...

Can you give more details on this? What is allocated?

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

util/interval/btree_based_interval.go, line 117 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

Should this be the same as interfaces.insertAt (with respect to your change)?

Add changes similar to `items.insertAt`. See my comment about `items.insertAt`.

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

as mentioned below, let's change the build tag to something more descriptive

Addressed in my below comments.

I really like the benchmarks you've made. Could we add some of these in a later commit/PR?

I will open another PR for the benchmarks after this PR is merged.

NewTree isn't called in any performance critical sections, right? If not, I wouldn't worry about its performance.

Yes, it is not called in any performance critical sections.

to merge this you'll need to rebase on master's HEAD, which may be a little nasty due to #9844

I will make the rebase after this round review in order to make it easier for you to review my comments.


Review status: 4 of 10 files reviewed at latest revision, 4 unresolved discussions, some commit checks failed.


util/interval/btree_based_interval.go, line 102 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

At the very least this would be allocating the closure, but you're correct that this is idiomatic so lets not worry about it in this initial review.

OK

util/interval/btree_based_interval.go, line 182 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

Sorry, that comment was cut off. I think I was initially going to say we should move some duplicated logic into newNode, but this is fine.

OK

util/interval/btree_based_interval.go, line 19 at r3 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

Since this build tag is global, could we change it to something a bit more contextually descriptive, maybe interval_btree?

Done.

Makefile, line 103 at r2 (raw file):

Previously, nvanbenschoten (Nathan VanBenschoten) wrote…

Once rebased, the diff in this file goes away, right?

Yes. `Makefile` has gone away in https://github.com//pull/8867/files. But I am not sure whether it will also go away in Reviewable.

Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

@nvanbenschoten any chance to have a look at my comments?

@knz knz added the do-not-merge bors won't merge a PR with this label. label Apr 25, 2017
@tamird tamird requested a review from nvanbenschoten April 27, 2017 14:41
@yaojingguo
Copy link
Contributor Author

@nvanbenschoten still no time to review my comments?

@bdarnell
Copy link
Contributor

@nvanbenschoten is on vacation; reassigning to @dt

@bdarnell bdarnell requested a review from dt May 14, 2017 21:12
@dt
Copy link
Member

dt commented May 16, 2017

looks like nathan's points were addressed?

I'm happy to help with the rebase here -- I rebased this over my /pkg mass-rename and some of the small changes since, and pushed it to dt/btree on origin (I think this PR might have been created before GitHub added the allow maintainers to edit button, so I don't seem to be able to push the rebase directly).

There's might be some interaction with 839fa7e to look at more closely, but as is, it at least seems to compile.

I think we'll want to move away from build tags quickly, so both impls are compiled and tested regularly and thus can't rot, even if we stick with the LLBR as the default for now, until this gets stability testing in production. But that change can be a follow up -- getting the code landed in master is a first step to preventing any refactoring and further rot.


Reviewed 3 of 9 files at r1, 2 of 11 files at r2, 1 of 5 files at r4.
Review status: 5 of 10 files reviewed at latest revision, 4 unresolved discussions, some commit checks failed.


Comments from Reviewable

@yaojingguo
Copy link
Contributor Author

@dt, Thanks for your review.

looks like nathan's points were addressed?

Yes. I have addressed all nathan's points. And I have checked allow maintainers to edit.

The reason that the compilation succeeds is that btree tag is not provided during the compilation. To merge this PR, 839fa7e needs to be addressed beside file relocations.

I agree that we should go away with the build tags.

If you think that this is the right way to go, I can work out a new commit addressing the following points:

  • commit 839fa7e
  • file relocations
  • removal of the build tags

Review status: 5 of 10 files reviewed at latest revision, 4 unresolved discussions, some commit checks failed.


Comments from Reviewable

@dt
Copy link
Member

dt commented May 17, 2017

@yaojingguo for the file relocations, if you want you to, you can grab the ref I rebased, b7f45b3, from the dt/btree branch on origin, and push that to your branch?

for the build tags, I poked a little around and I think we can pretty easily pull a common Tree interface out and have NewTree always return the llbrtree for now, but then optionally switch to a btree in the future via an envutil call (might look something vaguely like this).

@yaojingguo
Copy link
Contributor Author

@dt I have proceeded based on your commit 7eebf50 from your dt/treeswitch branch. I have successfully run make test PKG=./pkg/util/interval and make lint locally. make test PKG=./pkg/util/sql fails locally. But make test PKG=./pkg/util/sql also fails on the commit 61d1ee4 in the upstream master branch. So I think that the failure is not caused by my commit. After I push my commit, Example-ORMs fails on Team City. Build log has the following exception:

[22:22:57]W:	 [Step 2/2] :runMay 25, 2017 10:22:57 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
[22:22:57]W:	 [Step 2/2] ERROR: An I/O error occurred while sending to the backend.
[22:22:57]W:	 [Step 2/2] May 25, 2017 10:22:57 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
[22:22:57]W:	 [Step 2/2] ERROR: This connection has been closed.
[22:22:57]W:	 [Step 2/2] May 25, 2017 10:22:57 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections close
[22:22:57]W:	 [Step 2/2] ERROR: Collection leak detected: there are 1 unclosed connections upon shutting down pool jdbc:postgresql://localhost:60047/company_hibernate?sslmode=disable&user=root
[22:22:57]W:	 [Step 2/2] Exception in thread "main" org.hibernate.exception.JDBCConnectionException: Unable to release JDBC Connection used for DDL execution
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:115)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl.release(DdlTransactionIsolatorNonJtaImpl.java:86)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.doMigration(AbstractSchemaMigrator.java:133)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:177)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:66)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:309)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:445)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:710)
[22:22:57]W:	 [Step 2/2] 	at com.cockroachlabs.util.SessionUtil.<init>(SessionUtil.java:64)
[22:22:57]W:	 [Step 2/2] 	at com.cockroachlabs.util.SessionUtil.init(SessionUtil.java:68)
[22:22:57]W:	 [Step 2/2] 	at com.cockroachlabs.Application.initHibernate(Application.java:33)
[22:22:57]W:	 [Step 2/2] 	at com.cockroachlabs.Application.run(Application.java:28)
[22:22:57]W:	 [Step 2/2] 	at com.cockroachlabs.Application.main(Application.java:24)
[22:22:57]W:	 [Step 2/2] Caused by: org.postgresql.util.PSQLException: This connection has been closed.
[22:22:57]W:	 [Step 2/2] 	at org.postgresql.jdbc.PgConnection.checkClosed(PgConnection.java:786)
[22:22:57]W:	 [Step 2/2] 	at org.postgresql.jdbc.PgConnection.setAutoCommit(PgConnection.java:743)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.engine.jdbc.connections.internal.PooledConnections.add(PooledConnections.java:68)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.closeConnection(DriverManagerConnectionProviderImpl.java:195)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.releaseConnection(JdbcEnvironmentInitiator.java:185)
[22:22:57]W:	 [Step 2/2] 	at org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl.release(DdlTransactionIsolatorNonJtaImpl.java:83)
[22:22:57]W:	 [Step 2/2] 	... 11 more
[22:22:57]W:	 [Step 2/2]  FAILED
[22:22:57]W:	 [Step 2/2] 
[22:22:57]W:	 [Step 2/2] FAILURE: Build failed with an exception.
[22:22:57]W:	 [Step 2/2] 
[22:22:57]W:	 [Step 2/2] * What went wrong:
[22:22:57]W:	 [Step 2/2] Execution failed for task ':run'.
[22:22:57]W:	 [Step 2/2] > Process 'command '/usr/lib/jvm/java-8-openjdk-amd64/bin/java'' finished with non-zero exit value 1
[22:22:57]W:	 [Step 2/2] 
[22:22:57]W:	 [Step 2/2] * Try:
[22:22:57]W:	 [Step 2/2] Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
[22:22:57]W:	 [Step 2/2] 
[22:22:57]W:	 [Step 2/2] BUILD FAILED

How can I fix this Example-ORMs failure if it is related to my commit?


Review status: 0 of 11 files reviewed at latest revision, 4 unresolved discussions, some commit checks failed.


Comments from Reviewable

@jordanlewis
Copy link
Member

@yaojingguo please rebase your branch on a more recent master and push again - the failure above is not your fault and was caused by a regression that was fixed after your branch point.

yaojingguo and others added 2 commits May 26, 2017 21:10
Removing the build tags means we're always building both tree impls instead of hiding one via tags.
Currently NewTree is hardcoded to the current LLBR impl, but could be be made to switch on an env var
@yaojingguo
Copy link
Contributor Author

@jordanlewis. I have rebased my commits and Team City is happy now.
@dt Could you please have a look at my new commits?


Review status: 0 of 11 files reviewed at latest revision, 4 unresolved discussions, all commit checks successful.


Comments from Reviewable

1. Implement Iterator method for btree and add tests.
2. Restore Range.Equal method and fix the wrong logic of Range.Equal.
   The existing Equal method considers Range A and Range B to be equal
   if A's Start is equal to B's End and A's End is equal to B's Start.
   The correct logic is to consider Range A and Range B to be equal if
   A's Start is equal to B's Start and A's End is equal to B's End. See
   cockroachdb#16079.
3. Add complile time checks to ensure that btree and llrbTree implement
   the Tree interface.
4. s/llbrNode/llrbNode/g and s/llbrTree/llrbTree/g.
5. Move comment from llrbTree to Tree.
@dt dt removed the do-not-merge bors won't merge a PR with this label. label Jun 1, 2017
Copy link
Member

@dt dt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it probably makes sense to go ahead and merge this: it passes tests, and, since it leaves NewTree as returning the current LLRB tree implementation, it doesn't seem too scary.

With it merged, it'll be easier to run experiments with the implementation swapped, profile it, etc.

@bdarnell
Copy link
Contributor

bdarnell commented Jun 1, 2017

I haven't read this in detail, but I've skimmed it and it LGTM.

@a-robinson
Copy link
Contributor

This is really cool, @yaojingguo. Thanks for sticking with it!

@dt dt changed the title WIP perf: replace LLRB-tree with btree in interval.Tree perf: replace LLRB-tree with btree in interval.Tree Jun 1, 2017
@dt dt changed the title perf: replace LLRB-tree with btree in interval.Tree interval: add btree implementation of interval.Tree Jun 1, 2017
@dt dt merged commit f3b2908 into cockroachdb:master Jun 1, 2017
@spencerkimball
Copy link
Member

Nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

perf: replace LLRB-tree with btree in interval.Tree
10 participants