1+ using BenchmarkDotNet . Attributes ;
2+
3+ using ColumnizerLib ;
4+
5+ using LogExpert . Benchmarks . Support ;
6+ using LogExpert . Core . Classes . Log ;
7+
8+ namespace LogExpert . Benchmarks ;
9+
10+ [ MemoryDiagnoser ]
11+ [ RankColumn ]
12+ public class BufferIndexBenchmarks
13+ {
14+ private BufferIndex _index = null ! ;
15+ private int _totalLines ;
16+
17+ [ Params ( 100 , 1_000 , 10_000 ) ]
18+ public int BufferCount { get ; set ; }
19+
20+ private const int LinesPerBuffer = 500 ;
21+
22+ [ GlobalSetup ]
23+ public void Setup ( )
24+ {
25+ _index = new BufferIndex ( BufferCount , LinesPerBuffer ) ;
26+ _totalLines = BufferCount * LinesPerBuffer ;
27+
28+ var fakeFileInfo = new FakeLogFileInfo ( ) ;
29+
30+ using ( var _ = _index . AcquireWriteLock ( ) )
31+ {
32+ for ( int i = 0 ; i < BufferCount ; i ++ )
33+ {
34+ var buffer = new LogBuffer ( fakeFileInfo , LinesPerBuffer )
35+ {
36+ StartLine = i * LinesPerBuffer
37+ } ;
38+
39+ for ( int j = 0 ; j < LinesPerBuffer ; j ++ )
40+ {
41+ buffer . AddLine (
42+ new LogLine ( $ "line { i * LinesPerBuffer + j } ". AsMemory ( ) , i * LinesPerBuffer + j ) ,
43+ 0 ) ;
44+ }
45+
46+ _index . Add ( buffer ) ;
47+ }
48+ }
49+
50+ // Validate setup
51+ var snapshot = _index . CreateSnapshot ( ) ;
52+ if ( snapshot . BufferCount != BufferCount )
53+ {
54+ throw new InvalidOperationException ( $ "Setup failed: expected { BufferCount } buffers, got { snapshot . BufferCount } ") ;
55+ }
56+ }
57+
58+ [ GlobalCleanup ]
59+ public void Cleanup ( ) => _index . Dispose ( ) ;
60+
61+ /// <summary>
62+ /// Simulates tail-follow: reading the last 1000 lines sequentially.
63+ /// Should hit Layer 0 (thread-local cache) ~99% of the time.
64+ /// </summary>
65+ [ Benchmark ( Baseline = true ) ]
66+ public LogBuffer ? SequentialAccess ( )
67+ {
68+ using var _ = _index . AcquireReadLock ( ) ;
69+ LogBuffer ? last = null ;
70+ var start = Math . Max ( 0 , _totalLines - 1000 ) ;
71+ for ( int i = start ; i < _totalLines ; i ++ )
72+ {
73+ var logBufferEntry = _index . TryFindBuffer ( i ) ;
74+ if ( logBufferEntry . Found )
75+ {
76+ last = logBufferEntry . Buffer ;
77+ }
78+ }
79+ return last ;
80+ }
81+
82+ /// <summary>
83+ /// Simulates search/goto: deterministic stride across the full file.
84+ /// Co-prime stride visits buffers in non-sequential, non-repeating order.
85+ /// Exercises Layers 2 and 3 heavily.
86+ /// </summary>
87+ [ Benchmark ]
88+ public LogBuffer ? StrideAccess ( )
89+ {
90+ using var _ = _index . AcquireReadLock ( ) ;
91+ LogBuffer ? last = null ;
92+ var stride = _totalLines / 3 + 1 ;
93+ var lineNum = 0 ;
94+ for ( int i = 0 ; i < 1000 ; i ++ )
95+ {
96+ var logBufferEntry = _index . TryFindBuffer ( lineNum ) ;
97+ if ( logBufferEntry . Found )
98+ {
99+ last = logBufferEntry . Buffer ;
100+ }
101+
102+ lineNum = ( lineNum + stride ) % _totalLines ;
103+ }
104+
105+ return last ;
106+ }
107+
108+ /// <summary>
109+ /// Worst case for Layer 0: always crossing buffer boundaries.
110+ /// Exercises Layer 1 (adjacent prediction).
111+ /// </summary>
112+ [ Benchmark ]
113+ public LogBuffer ? BoundaryAccess ( )
114+ {
115+ using var _ = _index . AcquireReadLock ( ) ;
116+ LogBuffer ? last = null ;
117+
118+ for ( int i = 0 ; i < 1000 ; i ++ )
119+ {
120+ int lineNum = i * ( _totalLines / 1000 ) ;
121+ var logBufferEntry = _index . TryFindBuffer ( lineNum ) ;
122+ if ( logBufferEntry . Found )
123+ {
124+ last = logBufferEntry . Buffer ;
125+ }
126+ }
127+
128+ return last ;
129+ }
130+
131+ /// <summary>
132+ /// Simulates UI scrolling: page-sized jumps forward through the file.
133+ /// 50-line pages with 3x page jumps (fast scroll drag).
134+ /// Exercises Layer 0 within pages and Layers 1-2 on transitions.
135+ /// </summary>
136+ [ Benchmark ]
137+ public LogBuffer ? ScrollAccess ( )
138+ {
139+ using var _ = _index . AcquireReadLock ( ) ;
140+ LogBuffer ? last = null ;
141+ const int pageSize = 50 ;
142+ const int pageJump = pageSize * 3 ;
143+ var pageStart = 0 ;
144+
145+ for ( int page = 0 ; page < 20 && pageStart < _totalLines ; page ++ )
146+ {
147+ var pageEnd = Math . Min ( pageStart + pageSize , _totalLines ) ;
148+ for ( int line = pageStart ; line < pageEnd ; line ++ )
149+ {
150+ var logBufferEntry = _index . TryFindBuffer ( line ) ;
151+ if ( logBufferEntry . Found )
152+ {
153+ last = logBufferEntry . Buffer ;
154+ }
155+ }
156+
157+ pageStart += pageJump ;
158+ }
159+
160+ return last ;
161+ }
162+
163+ /// <summary>
164+ /// Measures LRU eviction cost at current scale.
165+ /// </summary>
166+ [ Benchmark ]
167+ public void EvictAndRepopulate ( )
168+ {
169+ _index . EvictLeastRecentlyUsed ( ) ;
170+ }
171+ }
0 commit comments