1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00
pry--pry/lib/pry/ring.rb

88 lines
2.1 KiB
Ruby
Raw Normal View History

2011-05-15 11:29:14 +02:00
class Pry
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# A ring is a thread-safe fixed-capacity array to which you can only add
# elements. Older entries are overwritten as you add new elements, so that the
# ring can never contain more than `max_size` elemens.
2011-05-15 11:29:14 +02:00
#
# @example
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# ring = Pry::Ring.new(3)
# ring << 1 << 2 << 3
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# ring.to_a #=> [1, 2, 3]
# ring << 4
2018-10-21 05:58:12 +08:00
# ring.to_a #=> [2, 3, 4]
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
#
# ring[0] #=> 2
# ring[-1] #=> 4
# ring.clear
# ring[0] #=> nil
#
# @api public
# @since v0.12.0
class Ring
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# @return [Integer] maximum buffer size
attr_reader :max_size
2011-05-15 11:29:14 +02:00
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# @return [Integer] how many objects were added during the lifetime of the
# ring
attr_reader :count
alias size count
2011-05-15 11:29:14 +02:00
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# @param [Integer] max_size Maximum buffer size. The buffer will start
# overwriting elements once its reaches its maximum capacity
def initialize(max_size)
@max_size = max_size
@mutex = Mutex.new
clear
2011-05-15 11:29:14 +02:00
end
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# Push `value` to the current index.
#
# @param [Object] value
# @return [self]
2011-05-15 11:29:14 +02:00
def <<(value)
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
@mutex.synchronize do
@buffer[count % max_size] = value
@count += 1
self
2011-05-15 11:29:14 +02:00
end
end
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# Read the value stored at `index`.
#
# @param [Integer, Range] index The element (if Integer) or elements
# (if Range) associated with `index`
# @return [Object, Array<Object>, nil] element(s) at `index`, `nil` if none
# exist
def [](index)
@mutex.synchronize do
return @buffer[(count + index) % max_size] if index.is_a?(Integer)
return @buffer[index] if count <= max_size
transpose_buffer_tail[index]
2011-05-15 11:29:14 +02:00
end
end
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# @return [Array<Object>] the buffer as unwinded array
2011-05-15 11:29:14 +02:00
def to_a
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
return @buffer.dup if count <= max_size
transpose_buffer_tail
2011-05-15 11:29:14 +02:00
end
ring: rewrite the class to improve API Currently, the Ring class is written with help of Hash as backend store. According to the comments, the implementation should behave like a circular buffer, however it doesn't. Upon reaching maximum capacity Ring doesn't replace old elements but keeps writing to new cells, deleting old cells, so that the hash contains `nil` entries. The new implementation is based on Array and seems to be closer to the actual Ring. Older elemens get overwritten with newer ones. This class also includes Enumerable, however none of our APIs take advantage of it, so it seems like an overkill. There was also a problem with with this API because of the above-mentioned nils. For example, if the ring exceeds its maximum size, then callin `Enumerable#first` on it returns `nil`. The new implementation deals with this with removal of Enumerable. The `#[]` syntax is preserved, and now `ring[0]` returns an actual element instead of `nil`. In case users need the Enumerable functionality, they can call `Ring#to_a` to build the array, which supports the wanted methods. As for the speed, the new implementation is: * slower overall because it's thread-safe * faster without mutexes for `#<<` * slower without mutexes for `#[]` Benchmark for old implementation: ``` Warming up -------------------------------------- Ring#<< 223.451k i/100ms Ring#[] 2.837k i/100ms Calculating ------------------------------------- Ring#<< 223.157B (± 3.4%) i/s - 778.097B Ring#[] 82.485M (± 9.4%) i/s - 402.602M in 4.957792s ``` Benchmark for this implementation: ``` Warming up -------------------------------------- Ring#<< 211.587k i/100ms Ring#[] 1.974k i/100ms Calculating ------------------------------------- Ring#<< 211.385B (± 2.8%) i/s - 698.439B Ring#[] 40.292M (±17.0%) i/s - 190.069M in 4.971195s ``` The benchmark: ```rb require './lib/pry' require 'benchmark/ips' Benchmark.ips do |x| empty_ring = Pry::Ring.new(100) populated_ring = Pry::Ring.new(100) 150.times { |i| populated_ring << i } x.report("Ring#<<") do |times| empty_ring << times end x.report("Ring#[]") do |times| populated_ring[0] populated_ring[1] populated_ring[2] populated_ring[-1] populated_ring[-2] populated_ring[-3] populated_ring[1..2] populated_ring[-2..-1] populated_ring[-2..3] populated_ring[0..-1] populated_ring[2..-1] populated_ring[-1..10] populated_ring[-1..0] populated_ring[-1..1] end end ```
2018-10-21 04:58:56 +08:00
# Clear the buffer and reset count.
# @return [void]
def clear
@mutex.synchronize do
@buffer = []
@count = 0
end
2011-05-15 11:29:14 +02:00
end
private
def transpose_buffer_tail
tail = @buffer.slice(count % max_size, @buffer.size)
tail.concat @buffer.slice(0, count % max_size)
end
2011-05-15 11:29:14 +02:00
end
end