Resolves #35715
RangeSet (the data structure used for keeping track of seen integral switch prong items) used to do a linear search for any overlapping ranges on every insert, leading to O(n^2) comparisons. This had the 'advantage' that the entire set only needs to be sorted once, when checking whether the whole value range of a given type has been covered.
It now instead keeps itself sorted at all times which means that we can use binary search and only perform O(n log n) comparisons. This turns out to be way faster for large amounts of switch prong items.
Performance points
measurements taken with crap, running release builds on macbook pro m1 base model
10 items
Benchmark 1 (63 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 80.5ms ± 1.30ms 77.5ms ... 84.1ms 1 ( 2%) 0%
peak_rss 85.0MB ± 40.0KB 84.9MB ... 85.0MB 0 ( 0%) 0%
Benchmark 2 (62 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 80.8ms ± 1.41ms 78.3ms ... 86.1ms 1 ( 2%) + 0.4% ± 0.6%
peak_rss 84.7MB ± 63.4KB 84.6MB ... 84.9MB 0 ( 0%) - 0.2% ± 0.0%
100 items
Benchmark 1 (62 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 81.3ms ± 1.37ms 78.5ms ... 86.3ms 1 ( 2%) 0%
peak_rss 85.1MB ± 48.6KB 85.0MB ... 85.2MB 1 ( 2%) 0%
Benchmark 2 (62 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 80.8ms ± 1.14ms 78.5ms ... 83.7ms 0 ( 0%) - 0.6% ± 0.5%
peak_rss 84.9MB ± 59.5KB 84.8MB ... 85.0MB 0 ( 0%) - 0.2% ± 0.0%
1000 items
Benchmark 1 (39 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 129ms ± 1.14ms 128ms ... 134ms 1 ( 3%) 0%
peak_rss 86.4MB ± 60.4KB 86.3MB ... 86.6MB 1 ( 3%) 0%
Benchmark 2 (61 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 83.1ms ± 1.28ms 80.8ms ... 85.5ms 0 ( 0%) ⚡- 35.7% ± 0.4%
peak_rss 86.4MB ± 70.9KB 86.2MB ... 86.5MB 2 ( 3%) - 0.0% ± 0.0%
2000 items
Benchmark 1 (19 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 276ms ± 2.03ms 274ms ... 281ms 0 ( 0%) 0%
peak_rss 87.9MB ± 106KB 87.8MB ... 88.3MB 1 ( 5%) 0%
Benchmark 2 (58 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 86.2ms ± 1.05ms 84.1ms ... 88.8ms 0 ( 0%) ⚡- 68.8% ± 0.3%
peak_rss 87.8MB ± 63.9KB 87.6MB ... 88.1MB 2 ( 3%) - 0.1% ± 0.0%
4000 items
Benchmark 1 (6 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 870ms ± 8.11ms 866ms ... 886ms 0 ( 0%) 0%
peak_rss 90.4MB ± 335KB 90.1MB ... 91.0MB 0 ( 0%) 0%
Benchmark 2 (54 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 94.1ms ± 949us 92.3ms ... 95.8ms 0 ( 0%) ⚡- 89.2% ± 0.3%
peak_rss 90.2MB ± 121KB 89.9MB ... 90.7MB 1 ( 2%) - 0.3% ± 0.1%
8000 items
Benchmark 1 (3 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 3.19s ± 1.91ms 3.18s ... 3.19s 0 ( 0%) 0%
peak_rss 95.2MB ± 904KB 94.7MB ... 96.2MB 0 ( 0%) 0%
Benchmark 2 (43 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 119ms ± 1.30ms 115ms ... 121ms 1 ( 2%) ⚡- 96.3% ± 0.1%
peak_rss 94.9MB ± 310KB 94.4MB ... 96.1MB 2 ( 5%) - 0.4% ± 0.5%
12000 items
Benchmark 1 (3 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 7.11s ± 82.0ms 7.04s ... 7.20s 0 ( 0%) 0%
peak_rss 101MB ± 1.01MB 99.9MB ... 102MB 0 ( 0%) 0%
Benchmark 2 (32 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 157ms ± 22.2ms 150ms ... 278ms 1 ( 3%) ⚡- 97.8% ± 0.5%
peak_rss 100MB ± 388KB 99.8MB ... 102MB 1 ( 3%) - 0.4% ± 0.6%
I used this small python script to generate valid switch statements with non-fully-ordered prong items (so that keeping the range set sorted actually has a cost):
script
import sys
from random import random
if len(sys.argv) != 3:
print("need out path and prong count!")
exit(1)
path = str(sys.argv[1])
count = int(sys.argv[2])
out = open(path, "w")
out.truncate(0)
out.write("export fn foo(x: i64) void {\n")
out.write(" switch (x) {\n")
prev = 0
for i in range(count):
if random() < .5:
start = prev + int(random() * 10) + 1
end = start + int(random() * 10) + 1
if (random() < .33):
out.write(f" -{end}...-{start} => {{}},\n")
else:
out.write(f" {start}...{end} => {{}},\n")
prev = end
else:
item = prev + int(random() * 10) + 1
if (random() < .33):
out.write(f" -{item} => {{}},\n")
else:
out.write(f" {item} => {{}},\n")
prev = item
out.write(" else => {},\n")
out.write(" }\n")
out.write("}\n")
out.close()
Resolves #35715
`RangeSet` (the data structure used for keeping track of seen integral switch prong items) used to do a linear search for any overlapping ranges on every insert, leading to O(n^2) comparisons. This had the 'advantage' that the entire set only needs to be sorted once, when checking whether the whole value range of a given type has been covered.
It now instead keeps itself sorted at all times which means that we can use binary search and only perform O(n log n) comparisons. This turns out to be way faster for large amounts of switch prong items.
<details>
<summary>Performance points</summary>
measurements taken with crap, running release builds on macbook pro m1 base model
## 10 items
```
Benchmark 1 (63 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 80.5ms ± 1.30ms 77.5ms ... 84.1ms 1 ( 2%) 0%
peak_rss 85.0MB ± 40.0KB 84.9MB ... 85.0MB 0 ( 0%) 0%
Benchmark 2 (62 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 80.8ms ± 1.41ms 78.3ms ... 86.1ms 1 ( 2%) + 0.4% ± 0.6%
peak_rss 84.7MB ± 63.4KB 84.6MB ... 84.9MB 0 ( 0%) - 0.2% ± 0.0%
```
## 100 items
```
Benchmark 1 (62 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 81.3ms ± 1.37ms 78.5ms ... 86.3ms 1 ( 2%) 0%
peak_rss 85.1MB ± 48.6KB 85.0MB ... 85.2MB 1 ( 2%) 0%
Benchmark 2 (62 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 80.8ms ± 1.14ms 78.5ms ... 83.7ms 0 ( 0%) - 0.6% ± 0.5%
peak_rss 84.9MB ± 59.5KB 84.8MB ... 85.0MB 0 ( 0%) - 0.2% ± 0.0%
```
## 1000 items
```
Benchmark 1 (39 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 129ms ± 1.14ms 128ms ... 134ms 1 ( 3%) 0%
peak_rss 86.4MB ± 60.4KB 86.3MB ... 86.6MB 1 ( 3%) 0%
Benchmark 2 (61 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 83.1ms ± 1.28ms 80.8ms ... 85.5ms 0 ( 0%) ⚡- 35.7% ± 0.4%
peak_rss 86.4MB ± 70.9KB 86.2MB ... 86.5MB 2 ( 3%) - 0.0% ± 0.0%
```
## 2000 items
```
Benchmark 1 (19 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 276ms ± 2.03ms 274ms ... 281ms 0 ( 0%) 0%
peak_rss 87.9MB ± 106KB 87.8MB ... 88.3MB 1 ( 5%) 0%
Benchmark 2 (58 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 86.2ms ± 1.05ms 84.1ms ... 88.8ms 0 ( 0%) ⚡- 68.8% ± 0.3%
peak_rss 87.8MB ± 63.9KB 87.6MB ... 88.1MB 2 ( 3%) - 0.1% ± 0.0%
```
## 4000 items
```
Benchmark 1 (6 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 870ms ± 8.11ms 866ms ... 886ms 0 ( 0%) 0%
peak_rss 90.4MB ± 335KB 90.1MB ... 91.0MB 0 ( 0%) 0%
Benchmark 2 (54 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 94.1ms ± 949us 92.3ms ... 95.8ms 0 ( 0%) ⚡- 89.2% ± 0.3%
peak_rss 90.2MB ± 121KB 89.9MB ... 90.7MB 1 ( 2%) - 0.3% ± 0.1%
```
## 8000 items
```
Benchmark 1 (3 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 3.19s ± 1.91ms 3.18s ... 3.19s 0 ( 0%) 0%
peak_rss 95.2MB ± 904KB 94.7MB ... 96.2MB 0 ( 0%) 0%
Benchmark 2 (43 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 119ms ± 1.30ms 115ms ... 121ms 1 ( 2%) ⚡- 96.3% ± 0.1%
peak_rss 94.9MB ± 310KB 94.4MB ... 96.1MB 2 ( 5%) - 0.4% ± 0.5%
```
## 12000 items
```
Benchmark 1 (3 runs): build/stage4-master-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 7.11s ± 82.0ms 7.04s ... 7.20s 0 ( 0%) 0%
peak_rss 101MB ± 1.01MB 99.9MB ... 102MB 0 ( 0%) 0%
Benchmark 2 (32 runs): build/stage4-feature-fast/bin/zig build-obj -fno-emit-bin switch_ranges.zig
measurement mean ± σ min ... max outliers delta
wall_time 157ms ± 22.2ms 150ms ... 278ms 1 ( 3%) ⚡- 97.8% ± 0.5%
peak_rss 100MB ± 388KB 99.8MB ... 102MB 1 ( 3%) - 0.4% ± 0.6%
```
</details>
I used this small python script to generate valid switch statements with non-fully-ordered prong items (so that keeping the range set sorted actually has a cost):
<details>
<summary>script</summary>
```py
import sys
from random import random
if len(sys.argv) != 3:
print("need out path and prong count!")
exit(1)
path = str(sys.argv[1])
count = int(sys.argv[2])
out = open(path, "w")
out.truncate(0)
out.write("export fn foo(x: i64) void {\n")
out.write(" switch (x) {\n")
prev = 0
for i in range(count):
if random() < .5:
start = prev + int(random() * 10) + 1
end = start + int(random() * 10) + 1
if (random() < .33):
out.write(f" -{end}...-{start} => {{}},\n")
else:
out.write(f" {start}...{end} => {{}},\n")
prev = end
else:
item = prev + int(random() * 10) + 1
if (random() < .33):
out.write(f" -{item} => {{}},\n")
else:
out.write(f" {item} => {{}},\n")
prev = item
out.write(" else => {},\n")
out.write(" }\n")
out.write("}\n")
out.close()
```
</details>