From 70bf29ea2ee12f6d43a362a017dc1741755bbfde Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 18 Apr 2023 16:30:35 +0800 Subject: [PATCH] Optimize `Array#concat(Indexable)` (#13280) --- spec/std/array_spec.cr | 14 ++++++++++++++ src/array.cr | 23 +++++++++++++++++++++-- src/deque.cr | 33 ++++++++++++++++++--------------- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index f465b49684ff..57e2e27c4334 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -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']) diff --git a/src/array.cr b/src/array.cr index 71dc841de1fe..f243d4a674e4 100644 --- a/src/array.cr +++ b/src/array.cr @@ -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 diff --git a/src/deque.cr b/src/deque.cr index 938f60d993af..2a2802dcfa95 100644 --- a/src/deque.cr +++ b/src/deque.cr @@ -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 @@ -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 @@ -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 @@ -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