66namespace LLama . Common
77{
88 /// <summary>
9- /// A queue with fixed storage size.
10- /// Currently it's only a naive implementation and needs to be further optimized in the future.
9+ /// A queue with fixed storage size backed by a circular buffer.
1110 /// </summary>
1211 public class FixedSizeQueue < T >
1312 : IReadOnlyList < T >
1413 {
15- private readonly List < T > _storage ;
14+ private readonly T [ ] _buffer ;
15+ private int _start ;
16+ private int _count ;
17+ private T [ ] ? _window ;
18+
19+ // Minimum capacity for the temporary buffer used to expose a contiguous view.
20+ private const int MinimumWindowSize = 4 ;
21+ // Resize multiplier for the temporary buffer to reduce copy churn as it grows.
22+ private const int WindowGrowthFactor = 2 ;
1623
1724 /// <inheritdoc />
18- public T this [ int index ] => _storage [ index ] ;
25+ public T this [ int index ]
26+ {
27+ get
28+ {
29+ if ( ( uint ) index >= ( uint ) _count )
30+ throw new ArgumentOutOfRangeException ( nameof ( index ) ) ;
31+
32+ var actualIndex = ( _start + index ) % Capacity ;
33+ return _buffer [ actualIndex ] ;
34+ }
35+ }
1936
2037 /// <summary>
2138 /// Number of items in this queue
2239 /// </summary>
23- public int Count => _storage . Count ;
40+ public int Count => _count ;
2441
2542 /// <summary>
2643 /// Maximum number of items allowed in this queue
2744 /// </summary>
2845 public int Capacity { get ; }
2946
3047 /// <summary>
31- /// Create a new queue
48+ /// Create a new queue.
3249 /// </summary>
33- /// <param name="size">the maximum number of items to store in this queue</param>
50+ /// <param name="size">The maximum number of items to store in this queue. </param>
3451 public FixedSizeQueue ( int size )
3552 {
53+ if ( size <= 0 )
54+ throw new ArgumentOutOfRangeException ( nameof ( size ) , size , "Capacity must be greater than zero." ) ;
55+
3656 Capacity = size ;
37- _storage = new ( ) ;
57+ _buffer = new T [ size ] ;
58+ _start = 0 ;
59+ _count = 0 ;
3860 }
3961
4062 /// <summary>
41- /// Fill the quene with the data. Please ensure that data.Count <= size
63+ /// Fill the queue with existing data. Please ensure that data.Count <= size
4264 /// </summary>
4365 /// <param name="size"></param>
4466 /// <param name="data"></param>
4567 public FixedSizeQueue ( int size , IEnumerable < T > data )
68+ : this ( size )
4669 {
4770#if NET6_0_OR_GREATER
48- // Try to check the size without enumerating the entire IEnumerable. This may not be able to get the count,
49- // in which case we'll have to check later
5071 if ( data . TryGetNonEnumeratedCount ( out var dataCount ) && dataCount > size )
51- throw new ArgumentException ( $ "The max size set for the quene is { size } , but got { dataCount } initial values.") ;
72+ throw new ArgumentException ( $ "The max size set for the queue is { size } , but got { dataCount } initial values.") ;
5273#endif
5374
54- // Size of "data" is unknown, copy it all into a list
55- Capacity = size ;
56- _storage = new List < T > ( data ) ;
75+ if ( data is ICollection < T > collection )
76+ {
77+ if ( collection . Count > size )
78+ throw new ArgumentException ( $ "The max size set for the queue is { size } , but got { collection . Count } initial values.") ;
79+
80+ foreach ( var item in collection )
81+ Enqueue ( item ) ;
82+ return ;
83+ }
5784
58- // Now check if that list is a valid size.
59- if ( _storage . Count > Capacity )
60- throw new ArgumentException ( $ "The max size set for the quene is { size } , but got { _storage . Count } initial values.") ;
85+ var index = 0 ;
86+ foreach ( var item in data )
87+ {
88+ if ( index >= size )
89+ throw new ArgumentException ( $ "The max size set for the queue is { size } , but got { index + 1 } initial values.") ;
90+
91+ Enqueue ( item ) ;
92+ index ++ ;
93+ }
6194 }
6295
6396 /// <summary>
64- /// Enquene an element.
97+ /// Enqueue an element. When the queue is full the oldest element is overwritten .
6598 /// </summary>
66- /// <returns></returns>
6799 public void Enqueue ( T item )
68100 {
69- _storage . Add ( item ) ;
70- if ( _storage . Count > Capacity )
71- _storage . RemoveAt ( 0 ) ;
101+ if ( _count < Capacity )
102+ {
103+ var tail = ( _start + _count ) % Capacity ;
104+ _buffer [ tail ] = item ;
105+ _count ++ ;
106+ }
107+ else
108+ {
109+ _buffer [ _start ] = item ;
110+ _start ++ ;
111+ if ( _start == Capacity )
112+ _start = 0 ;
113+ }
72114 }
73115
74116 /// <inheritdoc />
75117 public IEnumerator < T > GetEnumerator ( )
76118 {
77- return _storage . GetEnumerator ( ) ;
119+ return Enumerate ( ) . GetEnumerator ( ) ;
78120 }
79121
80122 /// <inheritdoc />
@@ -83,17 +125,12 @@ IEnumerator IEnumerable.GetEnumerator()
83125 return GetEnumerator ( ) ;
84126 }
85127
86- internal ReadOnlySpan < T > AsSpan ( int count )
128+ private IEnumerable < T > Enumerate ( )
87129 {
88- // Ensure the request isn't for more tokens than actually exist
89- count = Math . Min ( count , Count ) ;
90-
91- // Take `count` items from the end
92- #if NET8_0_OR_GREATER
93- return CollectionsMarshal . AsSpan ( _storage ) [ ^ count ..] ;
94- #else
95- return _storage . ToArray ( ) . AsSpan ( _storage . Count - count , count ) ;
96- #endif
130+ for ( var i = 0 ; i < _count ; i ++ )
131+ {
132+ yield return this [ i ] ;
133+ }
97134 }
98135 }
99136}
0 commit comments