Skip to content

Commit 09d2bc0

Browse files
committed
♻️ Refactor SequenceSet#dup, #clone, and #replace
This was originally part of some performance improvements. But any speedup reported by the (newly added) benchmark isn't significant. So this change is more for the semantics of these methods. Changes: * don't delegate `#initialize_clone` and `#replace` to `#initialize_dup` * call `#modifying!` and copy the string in `#replace` * don't call `#modifying!` in `#initialize_dup` or `#initialize_clone` * don't copy or build the string in `#initialize_dup`. `@string` is always frozen, so the default shallow copy is enough.
1 parent c79f860 commit 09d2bc0

File tree

2 files changed

+105
-5
lines changed

2 files changed

+105
-5
lines changed

benchmarks/sequence_set-copy.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
---
2+
prelude: |
3+
require "yaml"
4+
require "net/imap"
5+
6+
INPUT_COUNT = Integer ENV.fetch("BENCHMARK_INPUT_COUNT", 1000)
7+
MAX_INPUT = Integer ENV.fetch("BENCHMARK_MAX_INPUT", 1400)
8+
WARMUP_RUNS = Integer ENV.fetch("BENCHMARK_WARMUP_RUNS", 1000)
9+
10+
require "./test/lib/profiling_helper"
11+
include ProfilingHelper
12+
13+
def init_sets(count: 100, set_size: INPUT_COUNT, max: MAX_INPUT)
14+
Array.new(count) {
15+
Net::IMAP::SequenceSet.new(Array.new(set_size) { rand(1..max) })
16+
}
17+
end
18+
19+
def init_normal_sets(...)
20+
init_sets(...)
21+
end
22+
23+
def init_unsorted_sets(...)
24+
init_sets(...)
25+
.each do |seqset|
26+
entries = seqset.entries.shuffle
27+
seqset.clear
28+
entries.each do |entry|
29+
seqset.append entry
30+
end
31+
end
32+
end
33+
34+
# warmup (esp. for JIT)
35+
WARMUP_RUNS.times do
36+
init_sets(count: 20, set_size: 100, max: 120).each do |set|
37+
set.dup
38+
set.clone
39+
Net::IMAP::SequenceSet.new set
40+
end
41+
end
42+
43+
benchmark:
44+
- name: "normal.dup (#string not called)"
45+
prelude: $sets = init_normal_sets
46+
script: $sets.sample.dup
47+
- name: "normal.dup (#string called)"
48+
prelude: $sets = init_normal_sets.tap do _1.each(&:string) end
49+
script: $sets.sample.dup
50+
- name: "normal.freeze.dup"
51+
prelude: $sets = init_normal_sets
52+
script: $sets.sample.freeze.dup
53+
- name: "frozen.dup"
54+
prelude: $sets = init_normal_sets.map(&:freeze)
55+
script: $sets.sample.dup
56+
57+
- name: "normal.clone (#string not called)"
58+
prelude: $sets = init_normal_sets
59+
script: $sets.sample.clone
60+
- name: "normal.clone (#string called)"
61+
prelude: $sets = init_normal_sets.tap do _1.each(&:string) end
62+
script: $sets.sample.clone
63+
- name: "normal.freeze.clone"
64+
prelude: $sets = init_normal_sets
65+
script: $sets.sample.freeze.clone
66+
- name: "frozen.clone"
67+
prelude: $sets = init_normal_sets.map(&:freeze)
68+
script: $sets.sample.clone
69+
70+
- name: "SequenceSet.new(normal) (#string not called)"
71+
prelude: $sets = init_normal_sets
72+
script: Net::IMAP::SequenceSet.new $sets.sample
73+
- name: "SequenceSet.new(normal) (#string called)"
74+
prelude: $sets = init_normal_sets.tap do _1.each(&:string) end
75+
script: Net::IMAP::SequenceSet.new $sets.sample
76+
- name: "SequenceSet.new(frozen)"
77+
prelude: $sets = init_normal_sets.map(&:freeze)
78+
script: Net::IMAP::SequenceSet.new $sets.sample
79+
- name: "SequenceSet.new(unsorted)"
80+
prelude: $sets = init_unsorted_sets
81+
script: Net::IMAP::SequenceSet.new $sets.sample
82+
83+
contexts:
84+
- name: local
85+
prelude: |
86+
$LOAD_PATH.unshift "./lib"
87+
$allowed_to_profile = true # only profile local code
88+
require: false
89+
- name: v0.5.9
90+
gems:
91+
net-imap: 0.5.9
92+
require: false
93+
- name: v0.4.21
94+
gems:
95+
net-imap: 0.4.21
96+
require: false

lib/net/imap/sequence_set.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,10 @@ def clear
547547
# accepted by ::new.
548548
def replace(other)
549549
case other
550-
when SequenceSet then initialize_dup(other)
550+
when SequenceSet then
551+
modifying! # short circuit before doing any work
552+
@tuples = other.deep_copy_tuples
553+
@string = other.instance_variable_get(:@string)
551554
when String then self.string = other
552555
else clear; merge other
553556
end
@@ -1734,20 +1737,21 @@ def init_with(coder) # :nodoc:
17341737

17351738
attr_reader :tuples # :nodoc:
17361739

1740+
def deep_copy_tuples; @tuples.map { _1.dup } end # :nodoc:
1741+
17371742
private
17381743

17391744
def remain_frozen(set) frozen? ? set.freeze : set end
17401745
def remain_frozen_empty; frozen? ? SequenceSet.empty : SequenceSet.new end
17411746

17421747
# frozen clones are shallow copied
17431748
def initialize_clone(other)
1744-
other.frozen? ? super : initialize_dup(other)
1749+
@tuples = other.deep_copy_tuples unless other.frozen?
1750+
super
17451751
end
17461752

17471753
def initialize_dup(other)
1748-
modifying! # redundant check, to normalize the error message for JRuby
1749-
@tuples = other.tuples.map(&:dup)
1750-
@string = other.string&.-@
1754+
@tuples = other.deep_copy_tuples
17511755
super
17521756
end
17531757

0 commit comments

Comments
 (0)