Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ log := CTFIFOBuffer withCapacity: 1000.
| Operation | OrderedCollection | Array | CTFIFOBuffer |
|-----------|------------------|--------|-------------|
| Add item | O(1) | O(1) | O(1) |
| Remove old | O(n) | O(n) | O(1) |
| Remove old | O(n) | O(1) | O(1) |
| Memory usage | Unlimited growth | Fixed/reallocated | Fixed |

**Note**: OrderedCollection can cause memory spikes - when capacity is exceeded, it creates a new array 2x the size and copies all elements over, leading to O(n) performance degradation. In contrast, CTFIFOBuffer maintains constant O(1) performance with fixed memory usage.

## Contributing

Expand Down
104 changes: 66 additions & 38 deletions docs/Circular_Buffer.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,51 +153,79 @@ previousPage := browserHistory pop.

I conducted comprehensive performance tests comparing three approaches i.e Arrays, Circular Buffers & Ordered Collections for maintaining the last 100 items. Here's what the data reveals:

### Test Run 1: Individual Performance
### Test Run: Average Performance

![](Performance-Single-Run.png)

The first screenshot shows a single test run where I measured the execution time for each approach. You can see in the transcript:

- **Array**: 66ms
- **Buffer**: 140ms
- **OrderedCollection**: 299ms
```smalltalk
| totalOperations capacity arrayResults bufferResults orderedCollectionResults arrayMean bufferMean orderedMean arrayVariance bufferVariance orderedVariance |

totalOperations := 1000000.
capacity := 100.

arrayResults := OrderedCollection new.
bufferResults := OrderedCollection new.
orderedCollectionResults := OrderedCollection new.

Transcript show: 'Average result after running each benchmark for 100 times...'; cr.

100 timesRepeat: [
arrayResults add: ([
| array currentIndex |
array := Array new: capacity.
currentIndex := 1.
1 to: totalOperations do: [ :i |
array at: currentIndex put: i.
currentIndex := currentIndex \\ capacity + 1.
].
] timeToRun asMilliSeconds).

bufferResults add: ([
| buffer |
buffer := CTFIFOBuffer withCapacity: capacity.
1 to: totalOperations do: [ :i |
buffer push: i.
].
] timeToRun asMilliSeconds).

orderedCollectionResults add: ([
| collection |
collection := OrderedCollection new.
1 to: totalOperations do: [ :i |
collection add: i.
collection size > capacity ifTrue: [
collection removeFirst.
].
].
] timeToRun asMilliSeconds).
].

This shows the relative performance - OrderedCollection is clearly the slowest due to all those expensive removeFirst operations.
### Test Run 2: Average Performance
"Calculate averages and variance"
arrayMean := arrayResults sum / arrayResults size.
bufferMean := bufferResults sum / bufferResults size.
orderedMean := orderedCollectionResults sum / orderedCollectionResults size.

![](Performance-Average.png)
arrayVariance := (arrayResults collect: [ :each | (each - arrayMean) squared ]) sum / arrayResults size.
bufferVariance := (bufferResults collect: [ :each | (each - bufferMean) squared ]) sum / bufferResults size.
orderedVariance := (orderedCollectionResults collect: [ :each | (each - orderedMean) squared ]) sum / orderedCollectionResults size.

The second screenshot shows the average execution time over 100 test runs. The results are as follows:
"Display results"
Transcript show: 'Array: ', arrayMean rounded asString, ' ms (variance: ', (arrayVariance roundTo: 0.01) asString, ')'; cr.
Transcript show: 'Buffer: ', bufferMean rounded asString, ' ms (variance: ', (bufferVariance roundTo: 0.01) asString, ')'; cr.
Transcript show: 'OrderedCollection: ', orderedMean rounded asString, ' ms (variance: ', (orderedVariance roundTo: 0.01) asString, ')'; cr.
```

- **Array**: ~6 ms
- **Buffer**: ~14 ms
- **OrderedCollection**: ~31 ms
### Results (average execution time over 100 test runs):
- **Array**: ~6 ms (variance: low)
- **Buffer**: ~14 ms (variance: low)
- **OrderedCollection**: ~31 ms (variance: high)

These averages confirm the trends observed in the individual test run - Circular Buffers provide a solid middle ground between raw speed and ease of use, while Ordered Collections lag behind due to their inherent inefficiencies.
### Why These Results Matter

### Test Run 3: Performance Benchmark
![](Performance-Bench.png)
Using Pharo's bench method to measure sustained performance, measured in operations per 5 seconds. The results are:
**Array** is fastest but requires complex manual index management and wraparound logic - easy to introduce bugs.

- **Array**: ~756 iterations/5sec
- **Buffer**: ~351 iterations/5sec
- **OrderedCollection**: ~170 iterations/5sec
**Buffer** delivers excellent performance while eliminating all complexity. It's built on arrays internally, so it inherits array access costs plus method call overhead, making it ~2.3x slower than raw arrays. However, this trade-off is worth it because you get 43% of array speed with zero complexity.

### Why These Results Matter
**OrderedCollection** has multiple serious problems:
- **Slow removeFirst operations**: Shifts hundreds of elements every time
- **Memory spikes**: When internal capacity is exceeded, it creates a new array double the size and copies all elements, causing temporary memory usage spikes

- **Array (Manual Management)**
- Fastest performance
- Requires manual index logic and careful boundary checks
- Easy to introduce bugs & Hard to maintain

- **Circular Buffer**
- Nearly as fast as arrays
- Delivers 43% of Array speed while eliminating 100% of the complexity
- Automatically manages size
- Clean, safe, and maintainable for most use cases

- **OrderedCollection**
- Slowest performance
- Gets destroyed by `removeFirst` operations that shift hundreds of elements every time
- Not suitable for high-volume data management
This makes OrderedCollection unsuitable for high-volume streaming data or real-time applications.
Binary file removed docs/Performance-Average.png
Binary file not shown.
Binary file removed docs/Performance-Bench.png
Binary file not shown.
Binary file removed docs/Performance-Single-Run.png
Binary file not shown.
Loading