patternrubyMinor
Ring buffer in Ruby
Viewed 0 times
ringbufferruby
Problem
I was just reading about ring buffer the other day and I was fascinated by how simple yet efficient it is.
I've implemented a ring (circular) buffer in Ruby and I'm just wondering if there's anything I can improve, and also how to properly measure its performance.
Ring buffer:
Speed test:
I've implemented a ring (circular) buffer in Ruby and I'm just wondering if there's anything I can improve, and also how to properly measure its performance.
Ring buffer:
class RingBuffer
attr_accessor :ring, :size, :start, :end
def initialize(size)
@ring = Array.new(size)
@size = size
@start, @end = 0, 0
end
def full?
(@end + 1) % @size == @start
end
def empty?
@end == @start
end
def write(element)
return if full?
@ring[@end] = element
@end = (@end + 1) % @size
end
def read
return if empty?
element = @ring[@start]
@start = (@start + 1) % @size
element
end
def clear
@ring = Array.new(@size)
@start, @end = 0, 0
end
endSpeed test:
require_relative 'ring_buffer.rb'
buffer_size = 1024*1024
rb = RingBuffer.new(buffer_size)
t0 = Time.now
(buffer_size-1).times {|idx|
rb.write idx
}
t1 = Time.now
(buffer_size-1).times {|idx|
rb.read
}
t2 = Time.now
t_all = (t2-t0) * 1000.0
t_avg_w = (t1 - t0) * 1000.0 * 1000.0 / buffer_size
t_avg_r = (t2 - t1) * 1000.0 * 1000.0 / buffer_size
printf("All: %.02fms\n", t_all)
printf("Avg. write: %.02fμs\n", t_avg_w)
printf("Avg. read: %.02fμs\n", t_avg_r)Solution
A few things:
-
You declare accessors: Use them yourself. Don't access instance variables directly if you can help it; you'll keep you code more flexible if your object uses its own accessor methods. In other words: Drop most of those
-
However, for the most part, you'll only want public reader methods. Otherwise anyone can just mess with the internal
In fact, I'd probably pare it allll the way down to just
-
You could consider adding a method to do the oft-duplicated
As for performance: Using a normal array with
This:
gets me:
RingBuffer#write
0.560000 0.000000 0.560000 ( 0.563023)
RingBuffer#read
0.400000 0.000000 0.400000 ( 0.400563)
----------------------------------
Plain Array#push
0.120000 0.000000 0.120000 ( 0.139201)
Plain Array#shift
0.170000 0.000000 0.170000 ( 0.164827)
So writing/pushing is ~4.7 times faster, and reading/shifting is ~2.4 times faster with a regular ol' Array.
-
You declare accessors: Use them yourself. Don't access instance variables directly if you can help it; you'll keep you code more flexible if your object uses its own accessor methods. In other words: Drop most of those
@ chars (though you'll have to use self.end to distinguish it from the end keyword)-
However, for the most part, you'll only want public reader methods. Otherwise anyone can just mess with the internal
ring array from the outside (sure, you can always mess with stuff in Ruby objects, but making it part of your object's declared interface makes it too easy).In fact, I'd probably pare it allll the way down to just
read, write, empty? and full? with no public accessors and keep the rest private.-
You could consider adding a method to do the oft-duplicated
(x + 1) % size trick for you.As for performance: Using a normal array with
push and shift is much faster I'm afraid. And there's no fixed size. So if you want a fast FIFO buffer/queue in plain Ruby, use an array.This:
buffer_size = 1024**2
buffer = RingBuffer.new(buffer_size)
puts "RingBuffer#write"
puts Benchmark.measure {
buffer_size.times { |i| buffer.write(i) }
}
puts "RingBuffer#read"
puts Benchmark.measure {
buffer_size.times { buffer.read }
}
puts "----------------------------------"
array = []
puts "Plain Array#push"
puts Benchmark.measure {
buffer_size.times { |i| array << i }
}
puts "Plain Array#shift"
puts Benchmark.measure {
buffer_size.times { array.shift }
}gets me:
RingBuffer#write
0.560000 0.000000 0.560000 ( 0.563023)
RingBuffer#read
0.400000 0.000000 0.400000 ( 0.400563)
----------------------------------
Plain Array#push
0.120000 0.000000 0.120000 ( 0.139201)
Plain Array#shift
0.170000 0.000000 0.170000 ( 0.164827)
So writing/pushing is ~4.7 times faster, and reading/shifting is ~2.4 times faster with a regular ol' Array.
Code Snippets
buffer_size = 1024**2
buffer = RingBuffer.new(buffer_size)
puts "RingBuffer#write"
puts Benchmark.measure {
buffer_size.times { |i| buffer.write(i) }
}
puts "RingBuffer#read"
puts Benchmark.measure {
buffer_size.times { buffer.read }
}
puts "----------------------------------"
array = []
puts "Plain Array#push"
puts Benchmark.measure {
buffer_size.times { |i| array << i }
}
puts "Plain Array#shift"
puts Benchmark.measure {
buffer_size.times { array.shift }
}Context
StackExchange Code Review Q#45260, answer score: 6
Revisions (0)
No revisions yet.