Skip to content

Commit 6b82c4f

Browse files
committed
Eliminate implicit Array allocations from frequently-called constructors
1 parent 0aa0cf0 commit 6b82c4f

File tree

8 files changed

+81
-71
lines changed

8 files changed

+81
-71
lines changed

lib/stupidedi/reader/input.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ class Input
1818
:at, :encoding, type: StringPtr
1919

2020
def initialize(pointer, position)
21-
@pointer, @position =
22-
pointer, position
21+
@pointer = pointer
22+
@position = position
2323
end
2424

2525
# @endgroup

lib/stupidedi/reader/pointer.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ class Pointer
4646
# @return [Integer]
4747
attr_reader :length
4848

49-
def initialize(storage, offset=0, length=storage.length)
49+
def initialize(storage, offset, length)
5050
raise ArgumentError, "offset must be non-negative" if offset < 0
5151
raise ArgumentError, "length must be non-negative" if length < 0
5252
raise ArgumentError, "given length cannot exceed storage length" if length > storage.length
5353

54-
@storage, @offset, @length =
55-
storage, offset, length
54+
@storage = storage
55+
@offset = offset
56+
@length = length
5657
end
5758

5859
# @return [String]
@@ -338,7 +339,7 @@ class << Pointer
338339
def build(object)
339340
case object
340341
when String
341-
StringPtr.new(object)
342+
StringPtr.new(object, 0, object.length)
342343
when Pointer
343344
object
344345
else
@@ -348,7 +349,7 @@ def build(object)
348349
raise TypeError, "object must respond to length" \
349350
unless object.respond_to?(:length)
350351

351-
Pointer.new(object)
352+
Pointer.new(object, 0, object.length)
352353
end
353354
end
354355

lib/stupidedi/reader/tokenizer/result.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ class Fail < Result
2424
attr_accessor :fatal
2525

2626
def initialize(error, position, fatal = false)
27-
@error, @position, @fatal =
28-
error, position, fatal
27+
@error = error
28+
@position = position
29+
@fatal = fatal
2930
end
3031

3132
def fail?
@@ -59,8 +60,9 @@ class Done < Result
5960
attr_reader :position
6061

6162
def initialize(value, position, rest)
62-
@value, @position, @rest =
63-
value, position, rest
63+
@value = value
64+
@position = position
65+
@rest = rest
6466
end
6567

6668
def fail?

lib/stupidedi/tokens/component_element_tok.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class ComponentElementTok
1313
attr_reader :position
1414

1515
def initialize(value, position)
16-
@value, @position =
17-
value, position
16+
@value = value
17+
@position = position
1818
end
1919

2020
# @return [CompositeElementTok]

lib/stupidedi/tokens/composite_element_tok.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ class CompositeElementTok
1313
attr_reader :position
1414

1515
def initialize(component_toks, position)
16-
@component_toks, @position =
17-
component_toks, position
16+
@component_toks = component_toks
17+
@position = position
1818
end
1919

2020
# @return [CompositeElementTok]

lib/stupidedi/tokens/segment_tok.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ class SegmentTok
1616
attr_reader :position
1717

1818
def initialize(id, element_toks, position)
19-
@id, @element_toks, @position =
20-
id, element_toks, position
19+
@id = id
20+
@element_toks = element_toks
21+
@position = position
2122
end
2223

2324
# @return [SegmentTok]

lib/stupidedi/tokens/simple_element_tok.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class SimpleElementTok
1313
attr_reader :position
1414

1515
def initialize(value, position)
16-
@value, @position = value, position
16+
@value = value
17+
@position = position
1718
end
1819

1920
# @return [SimpleElementTok]

spec/lib/stupidedi/reader/string_ptr_spec.rb

Lines changed: 58 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ def suffix(pointer)
1414
pointer.storage[pointer.offset + pointer.length..-1]
1515
end
1616

17-
# Ruby < 2.3 doesn't support frozen string literals
18-
let(:lower) { "abcdefghijklmnopqrstuvwxyz abcdefghijklmnOPQRSTUVWXYZ".freeze }
19-
let(:upper) { "ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNopqrstuvwxyz".freeze }
17+
let(:lower) { "abcdefghijklmnopqrstuvwxyz abcdefghijklmnOPQRSTUVWXYZ".dup }
18+
let(:upper) { "ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNopqrstuvwxyz".dup }
2019

21-
# Ensure pointer is given a mutable String, because `#frozen?` is used by
22-
# StringPtr to determine when zero-copy operations are safe to perform.
23-
let(:lower_ptr) { pointer(String.new(lower)) }
24-
let(:upper_ptr) { pointer(String.new(upper)) }
20+
let(:lower_ptr) { pointer(lower.dup) }
21+
let(:upper_ptr) { pointer(upper.dup) }
2522

2623
describe "#to_s" do
2724
it "is called implicitly" do
@@ -30,19 +27,19 @@ def suffix(pointer)
3027

3128
context "when storage is shared" do
3229
allocation do
33-
value = lower_ptr.drop(10)
34-
result = nil
35-
expect{ result = value.to_str }.to allocate(String: 1)
36-
expect(result).to_not be_frozen
30+
a = lower_ptr.drop(10)
31+
b = nil
32+
expect{ b = a.to_str }.to allocate(String: 1)
33+
expect(b).to_not be_frozen
3734
end
3835
end
3936

4037
context "when storage is not shared" do
4138
allocation do
42-
value = lower_ptr
43-
result = nil
44-
expect { result = value.to_str }.to allocate(String: 1)
45-
expect(result).to_not be_frozen
39+
a = lower_ptr
40+
b = nil
41+
expect{ b = a.to_str }.to allocate(String: 1)
42+
expect(b).to_not be_frozen
4643
end
4744
end
4845
end
@@ -55,58 +52,68 @@ def suffix(pointer)
5552

5653
context "when storage is shared" do
5754
allocation do
58-
value = lower_ptr.drop(10)
59-
result = nil
60-
expect{ result = value.to_str }.to allocate(String: 1)
61-
expect(result).to_not be_frozen
55+
a = lower_ptr.drop(10)
56+
b = nil
57+
expect{ b = a.to_str }.to allocate(String: 1)
58+
expect(b).to_not be_frozen
6259
end
6360
end
6461

6562
context "when storage is not shared" do
6663
allocation do
67-
value = lower_ptr
68-
result = nil
69-
expect { result = value.to_str }.to allocate(String: 1)
70-
expect(result).to_not be_frozen
64+
a = lower_ptr
65+
b = nil
66+
expect{ b = a.to_str }.to allocate(String: 1)
67+
expect(b).to_not be_frozen
7168
end
7269
end
7370
end
7471

7572
describe "#==" do
76-
it "is reflexive" do
77-
expect(lower_ptr).to eq(lower_ptr)
78-
expect(upper_ptr).to eq(upper_ptr)
79-
end
73+
allocation "is reflexive" do
74+
a = lower_ptr
75+
b = upper_ptr
76+
result = nil
8077

81-
it "compares string pointers to plain strings" do
82-
expect(lower_ptr).to eq(lower)
83-
expect(upper_ptr).to eq(upper)
84-
end
78+
expect{ result = a == a }.to allocate(String: 0)
79+
expect(result).to be true
8580

86-
it "works on identical substrings" do
87-
expect(lower_ptr.drop(10).take(10)).to eq(lower_ptr.drop(10).take(10))
88-
expect(upper_ptr.drop(10).take(10)).to eq(upper_ptr.drop(10).take(10))
81+
expect{ result = a == b }.to allocate(String: 0)
82+
expect(result).to be false
8983
end
9084

91-
it "works on non-identical substrings" do
92-
expect(lower_ptr.take(10)).to eq(lower_ptr.drop(27).take(10))
93-
expect(upper_ptr.drop(15).take(10)).to eq(lower_ptr.drop(42).take(10))
85+
allocation "compares string pointers to plain strings" do
86+
a, a_ = lower_ptr, lower
87+
b, b_ = upper_ptr, upper
88+
result = nil
89+
90+
expect{ result = a == a_ }.to allocate(String: 0)
91+
expect(result).to be true
92+
93+
expect{ result = a == b_ }.to allocate(String: 0)
94+
expect(result).to be false
9495
end
9596

96-
it "is case-sensitive" do
97-
expect(lower_ptr).to_not eq(upper_ptr)
98-
expect(upper_ptr).to_not eq(lower_ptr)
97+
allocation "works on identical substrings" do
98+
a = lower_ptr.drop(10).take(10)
99+
a_ = lower_ptr.drop(10).take(10)
100+
result = nil
99101

100-
expect(lower_ptr).to_not eq(upper)
101-
expect(upper_ptr).to_not eq(lower)
102+
expect{ result = a == a_ }.to allocate(String: 0)
103+
expect(result).to be true
102104
end
103105

104-
allocation do
105-
value = lower_ptr
106-
other = upper_ptr
107-
expect { value == other }.to allocate(String: 0)
108-
expect { value == value }.to allocate(String: 0)
109-
expect { value == "zzz" }.to allocate(String: 0)
106+
allocation "works on non-identical substrings" do
107+
a = lower_ptr.take(10)
108+
a_ = lower_ptr.drop(27).take(10)
109+
b_ = upper_ptr.drop(27).take(10)
110+
result = nil
111+
112+
expect{ result = a == a_ }.to allocate(String: 0)
113+
expect(result).to be true
114+
115+
expect{ result = a == b_ }.to allocate(String: 0)
116+
expect(result).to be false
110117
end
111118
end
112119

@@ -326,7 +333,6 @@ def cost_of_match?(ncalls)
326333
# Precondition
327334
expect(suffix(b)).to start_with(c)
328335

329-
pending "Why does this allocate an Array?"
330336
expect{ d = b + c }.to allocate(String: 0, a.class => 1)
331337
expect(b).to eq("def")
332338
expect(c).to eq("gh")
@@ -383,7 +389,6 @@ def cost_of_match?(ncalls)
383389
# Precondition
384390
expect(suffix(b)).to start_with(c)
385391

386-
pending "Why does this allocate an Array?"
387392
expect{ d = b + c }.to allocate(String: 0, a.class => 1)
388393
expect(a).to eq("abcdefghi")
389394
expect(b).to eq("def")
@@ -436,16 +441,16 @@ def cost_of_match?(ncalls)
436441
it "is true on empty strings" do
437442
expect(lower_ptr.drop(10).take(0)).to be_blank
438443
expect(lower_ptr.drop(lower_ptr.length)).to be_blank
439-
expect(Stupidedi::Reader::Pointer.build("")).to be_blank
444+
expect(pointer("")).to be_blank
440445
end
441446

442447
it "is true on whitespace-only strings" do
443-
expect(Stupidedi::Reader::Pointer.build(" \r\n\t\v\f")).to be_blank
448+
expect(pointer(" \r\n\t\v\f")).to be_blank
444449
end
445450

446451
it "is false on strings with non-whitespace" do
447452
expect(lower_ptr).to_not be_blank
448-
expect(Stupidedi::Reader::Pointer.build(" \r\n\t\v\f x")).to_not be_blank
453+
expect(pointer(" \r\n\t\v\f x")).to_not be_blank
449454
end
450455
end
451456

0 commit comments

Comments
 (0)