Skip to content

Commit 46697cc

Browse files
committed
⚡️ Improve SequenceSet#xor performance by ~2x
Obviously, the performance improvement is highly dependant on what data you're using, whether YJIT is enabled, etc. I saw results ranging from 1.7x faster to 2.6x faster. The benchmark script is included. For a benchmark run using sets with 10k members: ``` new impl 79.061 (± 8.9%) i/s (12.65 ms/i) - 392.000 in 5.004322s old impl 32.736 (±15.3%) i/s (30.55 ms/i) - 162.000 in 5.052839s ``` The old implementation was ~2.42x slower. For a benchmark run using very sparse sets with 100 members: ``` new impl 4.295k (±13.5%) i/s (232.81 μs/i) - 21.476k in 5.102536s old impl 2.459k (±11.3%) i/s (406.69 μs/i) - 12.095k in 5.000148s ``` This time, the old implementation was ~1.75x slower. I have some other (much bigger) PRs that should give even bigger performance improvements, but this is simple and effective.
1 parent e1e0ecb commit 46697cc

File tree

3 files changed

+40
-1
lines changed

3 files changed

+40
-1
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ gem "test-unit"
1515
gem "test-unit-ruby-core", git: "https://github.com/ruby/test-unit-ruby-core"
1616

1717
gem "benchmark-driver", require: false
18+
gem "benchmark-ips", require: false
1819

1920
group :test do
2021
gem "simplecov", require: false

benchmarks/seqset-ops.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env ruby
2+
require "benchmark/ips"
3+
require "net/imap"
4+
5+
warmup = 1.0
6+
time = 5.0
7+
size_a = 10_000
8+
size_b = 10_000
9+
max_a = 14_000
10+
max_b = 14_000
11+
12+
SeqSet = Net::IMAP::SequenceSet
13+
a = SeqSet[Array.new(size_a) { rand(1..max_a) }]
14+
b = SeqSet[Array.new(size_b) { rand(1..max_b) }]
15+
16+
puts ?=*72
17+
puts "SequenceSet XOR implementations"
18+
Benchmark.ips do |x|
19+
x.config(warmup:, time:)
20+
21+
# the original was missing the "a.dup", so it crashed or mutated a!
22+
x.report("a ^ b") do a.dup ^ b end
23+
x.report("new (a - b) | (b - a)") do
24+
SeqSet.new(a).subtract(b).merge(SeqSet.new(b).subtract(a))
25+
end
26+
x.report("dup (a - b) | (b - a)") do
27+
a.dup.subtract(b).merge(b.dup.subtract(a))
28+
end
29+
x.report("(a.dup | b).subtract(a & b)") do (a.dup | b).subtract(a & b) end
30+
x.report("dup (a | b) - (a & b)") do a.dup.merge(b).subtract(a & b) end
31+
32+
x.report("(a - b) | (b - a)") do (a - b) | (b - a) end
33+
x.report("(a | b) - (a & b)") do (a | b) - (a & b) end
34+
35+
x.compare!
36+
end

lib/net/imap/sequence_set.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,9 @@ def &(other)
702702
#
703703
# <tt>(seqset ^ other)</tt> is equivalent to <tt>((seqset | other) -
704704
# (seqset & other))</tt>.
705-
def ^(other) remain_frozen (dup | other).subtract(self & other) end
705+
def ^(other)
706+
remain_frozen dup.subtract(SequenceSet.new(other).subtract(self))
707+
end
706708
alias xor :^
707709

708710
# :call-seq:

0 commit comments

Comments
 (0)