Skip to content

Commit

Permalink
Optimize Array#concat(Indexable) (#13280)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil authored Apr 18, 2023
1 parent 7f885dd commit 70bf29e
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 17 deletions.
14 changes: 14 additions & 0 deletions spec/std/array_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,20 @@ describe "Array" do
a.@capacity.should eq(6)
end

it "concats indexable" do
a = [1, 2, 3]
a.concat(Slice.new(97) { |i| i + 4 })
a.should eq((1..100).to_a)

a = [1, 2, 3]
a.concat(StaticArray(Int32, 97).new { |i| i + 4 })
a.should eq((1..100).to_a)

a = [1, 2, 3]
a.concat(Deque.new(97) { |i| i + 4 })
a.should eq((1..100).to_a)
end

it "concats a union of arrays" do
a = [1, '2']
a.concat([3] || ['4'])
Expand Down
23 changes: 21 additions & 2 deletions src/array.cr
Original file line number Diff line number Diff line change
Expand Up @@ -746,18 +746,37 @@ class Array(T)
# ary.concat(["c", "d"])
# ary # => ["a", "b", "c", "d"]
# ```
def concat(other : Array) : self
def concat(other : Indexable) : self
other_size = other.size

resize_if_cant_insert(other_size)

(@buffer + @size).copy_from(other.to_unsafe, other_size)
concat_indexable(other)

@size += other_size

self
end

private def concat_indexable(other : Array | Slice | StaticArray)
(@buffer + @size).copy_from(other.to_unsafe, other.size)
end

private def concat_indexable(other : Deque)
ptr = @buffer + @size
Deque.half_slices(other) do |slice|
ptr.copy_from(slice.to_unsafe, slice.size)
ptr += slice.size
end
end

private def concat_indexable(other)
appender = (@buffer + @size).appender
other.each do |elem|
appender << elem
end
end

# :ditto:
def concat(other : Enumerable) : self
left_before_resize = remaining_capacity - @size
Expand Down
33 changes: 18 additions & 15 deletions src/deque.cr
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Deque(T)
@start = 0
protected setter size
protected getter buffer
protected getter capacity

# Creates a new empty Deque
def initialize
Expand Down Expand Up @@ -150,8 +151,8 @@ class Deque(T)

# Removes all elements from `self`.
def clear
halfs do |r|
(@buffer + r.begin).clear(r.end - r.begin)
Deque.half_slices(self) do |slice|
slice.to_unsafe.clear(slice.size)
end
@size = 0
@start = 0
Expand Down Expand Up @@ -339,9 +340,9 @@ class Deque(T)
#
# Do not modify the deque while using this variant of `each`!
def each(& : T ->) : Nil
halfs do |r|
r.each do |i|
yield @buffer[i]
Deque.half_slices(self) do |slice|
slice.each do |elem|
yield elem
end
end
end
Expand Down Expand Up @@ -564,20 +565,22 @@ class Deque(T)
self
end

private def halfs(&)
# :nodoc:
def self.half_slices(deque : Deque, &)
# For [----] yields nothing
# For contiguous [-012] yields 1...4
# For separated [234---01] yields 6...8, 0...3
# For contiguous [-012] yields @buffer[1...4]
# For separated [234---01] yields @buffer[6...8], @buffer[0...3]

return if empty?
a = @start
b = @start + size
b -= @capacity if b > @capacity
return if deque.empty?
a = deque.@start
b = deque.@start + deque.size
b -= deque.capacity if b > deque.capacity
if a < b
yield a...b
# TODO: this `typeof` is a workaround for 1.0.0; remove it eventually
yield Slice(typeof(deque.buffer.value)).new(deque.buffer + a, deque.size)
else
yield a...@capacity
yield 0...b
yield Slice(typeof(deque.buffer.value)).new(deque.buffer + a, deque.capacity - a)
yield Slice(typeof(deque.buffer.value)).new(deque.buffer, b)
end
end

Expand Down

0 comments on commit 70bf29e

Please sign in to comment.