1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00
pry--pry/spec/ring_spec.rb
Kyrylo Silin a4e9dd2e7d 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 05:31:45 +08:00

107 lines
2.5 KiB
Ruby

require_relative 'helper'
describe Pry::Ring do
let(:ring) { described_class.new(3) }
describe "#<<" do
it "adds elements as is when the ring is not full" do
ring << 1 << 2 << 3
expect(ring.to_a).to eq([1, 2, 3])
end
it "overwrites elements when the ring is full" do
ring << 1 << 2 << 3 << 4 << 5
expect(ring.to_a).to eq([3, 4, 5])
end
end
describe "#[]" do
context "when the ring is empty" do
it "returns nil" do
expect(ring[0]).to be_nil
end
end
context "when the ring is not full" do
before { ring << 1 << 2 << 3 }
it "reads elements" do
expect(ring[0]).to eq(1)
expect(ring[1]).to eq(2)
expect(ring[2]).to eq(3)
expect(ring[-1]).to eq(3)
expect(ring[-2]).to eq(2)
expect(ring[-3]).to eq(1)
end
it "reads elements via range" do
expect(ring[1..2]).to eq([2, 3])
expect(ring[-2..-1]).to eq([2, 3])
end
end
context "when the ring is full" do
before { ring << 1 << 2 << 3 << 4 << 5 }
it "reads elements" do
expect(ring[0]).to eq(3)
expect(ring[1]).to eq(4)
expect(ring[2]).to eq(5)
expect(ring[-1]).to eq(5)
expect(ring[-2]).to eq(4)
expect(ring[-3]).to eq(3)
end
it "reads elements via inclusive range" do
expect(ring[1..2]).to eq([4, 5])
expect(ring[-2..-1]).to eq([4, 5])
expect(ring[-2..3]).to eq([4, 5])
expect(ring[0..-1]).to eq([3, 4, 5])
expect(ring[2..-1]).to eq([5])
expect(ring[-1..10]).to eq([5])
expect(ring[-1..0]).to eq([])
expect(ring[-1..1]).to eq([])
end
it "reads elements via exclusive range" do
expect(ring[1...2]).to eq([4])
expect(ring[-2...-1]).to eq([4])
expect(ring[-2...3]).to eq([4, 5])
expect(ring[0...-1]).to eq([3, 4])
expect(ring[2...-1]).to eq([])
expect(ring[-1...10]).to eq([5])
expect(ring[-1...0]).to eq([])
expect(ring[-1...1]).to eq([])
end
end
end
describe "#to_a" do
it "returns a duplicate of internal buffer" do
array = ring.to_a
ring << 1
expect(array.count).to eq(0)
expect(ring.count).to eq(1)
end
end
describe "#clear" do
it "resets ring to initial state" do
ring << 1
expect(ring.count).to eq(1)
expect(ring.to_a).to eq([1])
ring.clear
expect(ring.count).to eq(0)
expect(ring.to_a).to eq([])
end
end
end