1+ using BenchmarkDotNet . Attributes ;
2+ using BenchmarkDotNet . Diagnosers ;
3+
4+ using ColumnizerLib ;
5+
6+ using LogExpert . Benchmarks . Support ;
7+ using LogExpert . Core . Classes . Log . Buffers ;
8+
9+ namespace LogExpert . Benchmarks ;
10+
11+ /// <summary>
12+ /// Measures ReaderWriterLockSlim contention under concurrent read load.
13+ /// Compares single-threaded throughput against N concurrent readers
14+ /// to determine if RWLS is a bottleneck worth optimizing.
15+ /// </summary>
16+ [ MemoryDiagnoser ]
17+ [ ThreadingDiagnoser ] // Reports lock contention + thread pool stats
18+ [ RankColumn ]
19+ public class BufferIndexContentionBenchmarks : IDisposable
20+ {
21+ private BufferIndex _index = null ! ;
22+ private int _totalLines ;
23+ private bool _disposed ;
24+
25+ private const int BUFFERS = 10_000 ;
26+ private const int LINES_PER_BUFFER = 500 ;
27+ private const int READS_PER_TASK = 1_000 ;
28+
29+ [ GlobalSetup ]
30+ public void Setup ( )
31+ {
32+ _index = new BufferIndex ( BUFFERS , LINES_PER_BUFFER ) ;
33+ _totalLines = BUFFERS * LINES_PER_BUFFER ;
34+
35+ var fakeFileInfo = new FakeLogFileInfo ( ) ;
36+ using var writeLock = _index . AcquireWriteLock ( ) ;
37+ for ( int i = 0 ; i < BUFFERS ; i ++ )
38+ {
39+ var buffer = new LogBuffer ( fakeFileInfo , LINES_PER_BUFFER )
40+ {
41+ StartLine = i * LINES_PER_BUFFER
42+ } ;
43+ for ( int j = 0 ; j < LINES_PER_BUFFER ; j ++ )
44+ {
45+ buffer . AddLine (
46+ new LogLine ( $ "line { i * LINES_PER_BUFFER + j } ". AsMemory ( ) ,
47+ i * LINES_PER_BUFFER + j ) , 0 ) ;
48+ }
49+ _index . Add ( buffer ) ;
50+ }
51+ }
52+
53+ /// <summary>
54+ /// Single-threaded baseline: sequential reads under one read lock.
55+ /// This is the ideal throughput ceiling.
56+ /// </summary>
57+ [ Benchmark ( Baseline = true ) ]
58+ public int SingleThreadedReads ( )
59+ {
60+ int found = 0 ;
61+ using var readLock = _index . AcquireReadLock ( ) ;
62+ var start = Math . Max ( 0 , _totalLines - READS_PER_TASK ) ;
63+ for ( int i = start ; i < _totalLines ; i ++ )
64+ {
65+ if ( _index . TryFindBuffer ( i ) . Found )
66+ {
67+ found ++ ;
68+ }
69+ }
70+
71+ return found ;
72+ }
73+
74+ /// <summary>
75+ /// N concurrent readers each acquiring their own read lock.
76+ /// If RWLS has no contention, throughput ≈ N × single-threaded.
77+ /// </summary>
78+ [ Benchmark ]
79+ [ Arguments ( 2 ) ]
80+ [ Arguments ( 4 ) ]
81+ [ Arguments ( 8 ) ]
82+ [ Arguments ( 12 ) ]
83+ public int ConcurrentReads ( int threadCount )
84+ {
85+ var total = 0 ;
86+ _ = Parallel . For ( 0 , threadCount , _ =>
87+ {
88+ int found = 0 ;
89+ using var readLock = _index . AcquireReadLock ( ) ;
90+ var start = Math . Max ( 0 , _totalLines - READS_PER_TASK ) ;
91+ for ( int i = start ; i < _totalLines ; i ++ )
92+ {
93+ if ( _index . TryFindBuffer ( i ) . Found )
94+ {
95+ found ++ ;
96+ }
97+ }
98+ _ = Interlocked . Add ( ref total , found ) ;
99+ } ) ;
100+ return total ;
101+ }
102+
103+ /// <summary>
104+ /// Simulates production: N readers + 1 writer (tail-follow append).
105+ /// Writer acquires write lock briefly every ~1000 reads.
106+ /// This is the realistic contention scenario.
107+ /// </summary>
108+ [ Benchmark ]
109+ [ Arguments ( 4 ) ]
110+ [ Arguments ( 8 ) ]
111+ public int ConcurrentReadsWithWriter ( int readerCount )
112+ {
113+ using var cts = new CancellationTokenSource ( ) ;
114+ var total = 0 ;
115+
116+ // Writer task: periodically takes write lock (simulates new buffer append)
117+ var writerTask = Task . Run ( ( ) =>
118+ {
119+ while ( ! cts . Token . IsCancellationRequested )
120+ {
121+ using var writeLock = _index . AcquireWriteLock ( ) ;
122+ // Simulate brief write work (no actual mutation to keep state clean)
123+ Thread . SpinWait ( 100 ) ;
124+ }
125+ } ) ;
126+
127+ // Reader tasks
128+ _ = Parallel . For ( 0 , readerCount , _ =>
129+ {
130+ int found = 0 ;
131+ using var readLock = _index . AcquireReadLock ( ) ;
132+ var start = Math . Max ( 0 , _totalLines - READS_PER_TASK ) ;
133+ for ( int i = start ; i < _totalLines ; i ++ )
134+ {
135+ if ( _index . TryFindBuffer ( i ) . Found )
136+ {
137+ found ++ ;
138+ }
139+ }
140+
141+ _ = Interlocked . Add ( ref total , found ) ;
142+ } ) ;
143+
144+ cts . Cancel ( ) ;
145+ writerTask . Wait ( ) ;
146+ return total ;
147+ }
148+
149+ [ GlobalCleanup ]
150+ public void Cleanup ( ) => _index . Dispose ( ) ;
151+
152+ public void Dispose ( )
153+ {
154+ Dispose ( true ) ;
155+ GC . SuppressFinalize ( this ) ;
156+ }
157+
158+ protected virtual void Dispose ( bool disposing )
159+ {
160+ if ( ! _disposed )
161+ {
162+ if ( disposing )
163+ {
164+ _index ? . Dispose ( ) ;
165+ }
166+
167+ _disposed = true ;
168+ }
169+ }
170+ }
0 commit comments