master
c 434 lines 16.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "benchmark-rw.h"
4
5 #define MAX_THREADS 64
6 #define TEST_DURATION_SEC 1
7 #define STOP_SIGNAL UINT64_MAX
8 #define MAX_CONFIGS 10
9
10 // Structure to store summary statistics
11 typedef struct {
12 double ops_per_sec[2][MAX_CONFIGS]; // [lock_type][config_index], Total ops/sec
13 double reader_ops_per_sec[2][MAX_CONFIGS]; // [lock_type][config_index], Reader ops/sec
14 double writer_ops_per_sec[2][MAX_CONFIGS]; // [lock_type][config_index], Writer ops/sec
15 int readers[MAX_CONFIGS]; // Number of readers for each config
16 int writers[MAX_CONFIGS]; // Number of writers for each config
17 int config_count; // Number of configurations tested
18 } summary_stats_t;
19
20 typedef struct {
21 // Protected state to validate reader/writer mutual exclusion
22 volatile int readers; // Number of active readers
23 volatile int writers; // Number of active writers
24 volatile uint64_t violations; // Counter for reader/writer violations
25
26 // Protected counter for actual work
27 uint64_t counter;
28
29 // Statistics per thread
30 struct {
31 uint64_t operations; // Number of read/write operations
32 usec_t test_time; // Time spent in test
33 volatile int ready; // Thread completed flag
34 } stats[MAX_THREADS];
35
36 // Per-thread control
37 struct {
38 netdata_cond_t cond; // Thread start condition
39 netdata_mutex_t cond_mutex; // Mutex for condition
40 uint64_t run_flag; // Thread run control
41 } thread_controls[MAX_THREADS];
42 } rwlock_control_t;
43
44 typedef enum {
45 THREAD_READER,
46 THREAD_WRITER
47 } thread_type_t;
48
49 typedef struct {
50 int thread_id;
51 thread_type_t type;
52 void *lock; // Points to either netdata_rwlock_t or RW_SPINLOCK
53 bool is_spinlock; // true for RW_SPINLOCK, false for netdata_rwlock_t
54 rwlock_control_t *control;
55 ND_THREAD *thread;
56 } thread_context_t;
57
58 static inline void verify_no_violations(rwlock_control_t *control) {
59 if(__atomic_load_n(&control->violations, __ATOMIC_RELAXED) > 0) {
60 fprintf(stderr, "\nFATAL ERROR: Detected %"PRIu64" read/write violations!\n"
61 "This indicates readers and writers were concurrently inside the lock.\n",
62 control->violations);
63 exit(1);
64 }
65 }
66
67 static inline void check_access_safety(rwlock_control_t *control, thread_type_t type) {
68 if(type == THREAD_READER) {
69 // Reader entering critical section
70 __atomic_add_fetch(&control->readers, 1, __ATOMIC_RELAXED);
71
72 // Check if we have any writers - this would be a violation
73 if(__atomic_load_n(&control->writers, __ATOMIC_RELAXED) > 0) {
74 __atomic_add_fetch(&control->violations, 1, __ATOMIC_RELAXED);
75 }
76 }
77 else {
78 // Writer entering critical section
79 int writers = __atomic_add_fetch(&control->writers, 1, __ATOMIC_RELAXED);
80
81 // Check for other writers - violation!
82 if(writers > 1) {
83 __atomic_add_fetch(&control->violations, 1, __ATOMIC_RELAXED);
84 }
85
86 // Check if we have any readers - this would be a violation
87 if(__atomic_load_n(&control->readers, __ATOMIC_RELAXED) > 0) {
88 __atomic_add_fetch(&control->violations, 1, __ATOMIC_RELAXED);
89 }
90 }
91 }
92
93 static void release_access(rwlock_control_t *control, thread_type_t type) {
94 if(type == THREAD_READER) {
95 __atomic_sub_fetch(&control->readers, 1, __ATOMIC_RELAXED);
96 }
97 else {
98 __atomic_sub_fetch(&control->writers, 1, __ATOMIC_RELAXED);
99 }
100 }
101
102 static void wait_for_start(netdata_cond_t *cond, netdata_mutex_t *mutex, uint64_t *flag) {
103 netdata_mutex_lock(mutex);
104 while (*flag == 0)
105 netdata_cond_wait(cond, mutex);
106 netdata_mutex_unlock(mutex);
107 }
108
109 static void benchmark_thread(void *arg) {
110 thread_context_t *ctx = (thread_context_t *)arg;
111 rwlock_control_t *control = ctx->control;
112
113 while(1) {
114 // Wait for start signal
115 wait_for_start(&control->thread_controls[ctx->thread_id].cond,
116 &control->thread_controls[ctx->thread_id].cond_mutex,
117 &control->thread_controls[ctx->thread_id].run_flag);
118
119 if (control->thread_controls[ctx->thread_id].run_flag == STOP_SIGNAL)
120 break;
121
122 usec_t start = now_monotonic_high_precision_usec();
123 uint64_t operations = 0;
124
125 while (control->thread_controls[ctx->thread_id].run_flag) {
126 if(ctx->is_spinlock) {
127 RW_SPINLOCK *spinlock = ctx->lock;
128 if(ctx->type == THREAD_READER) {
129 rw_spinlock_read_lock(spinlock);
130 check_access_safety(control, THREAD_READER);
131 control->counter++; // Just to do some work
132 release_access(control, THREAD_READER);
133 rw_spinlock_read_unlock(spinlock);
134 }
135 else {
136 rw_spinlock_write_lock(spinlock);
137 check_access_safety(control, THREAD_WRITER);
138 control->counter++;
139 release_access(control, THREAD_WRITER);
140 rw_spinlock_write_unlock(spinlock);
141 }
142 }
143 else {
144 netdata_rwlock_t *rwlock = ctx->lock;
145 if(ctx->type == THREAD_READER) {
146 netdata_rwlock_rdlock(rwlock);
147 check_access_safety(control, THREAD_READER);
148 control->counter++;
149 release_access(control, THREAD_READER);
150 netdata_rwlock_rdunlock(rwlock);
151 }
152 else {
153 netdata_rwlock_wrlock(rwlock);
154 check_access_safety(control, THREAD_WRITER);
155 control->counter++;
156 release_access(control, THREAD_WRITER);
157 netdata_rwlock_rdunlock(rwlock);
158 }
159 }
160 operations++;
161 }
162
163 // Store results
164 usec_t test_time = now_monotonic_high_precision_usec() - start;
165 __atomic_store_n(&control->stats[ctx->thread_id].test_time, test_time, __ATOMIC_RELEASE);
166 __atomic_store_n(&control->stats[ctx->thread_id].operations, operations, __ATOMIC_RELEASE);
167 __atomic_store_n(&control->stats[ctx->thread_id].ready, 1, __ATOMIC_RELEASE);
168 }
169 }
170
171 static void print_summary(const summary_stats_t *summary) {
172 fprintf(stderr, "\n=== Performance Summary (Million operations/sec) ===\n\n");
173 fprintf(stderr, "%-16s %-8s %-8s %-16s %-16s\n",
174 "Lock Type", "Readers", "Writers", "Reader Ops/s", "Writer Ops/s");
175 fprintf(stderr, "----------------------------------------------------------------------\n");
176
177 const char *lock_names[] = {"netdata_rwlock", "rw_spinlock"};
178
179 for (int config = 0; config < summary->config_count; config++) {
180 for (int lock_type = 0; lock_type < 2; lock_type++) {
181 // double total_ops = summary->ops_per_sec[lock_type][config];
182 int readers = summary->readers[config];
183 int writers = summary->writers[config];
184
185 // Get the actual reader and writer operations
186 double reader_ops = readers > 0 ? summary->reader_ops_per_sec[lock_type][config] : 0;
187 double writer_ops = writers > 0 ? summary->writer_ops_per_sec[lock_type][config] : 0;
188
189 fprintf(stderr, "%-16s %-8d %-8d %-16.2f %-16.2f\n",
190 lock_names[lock_type],
191 readers,
192 writers,
193 reader_ops / 1000000.0,
194 writer_ops / 1000000.0);
195 }
196 // Add a separator between configurations
197 if (config < summary->config_count - 1)
198 fprintf(stderr, "----------------------------------------------------------------------\n");
199 }
200 fprintf(stderr, "\n");
201 }
202
203 static void print_thread_stats(const char *test_name, int readers, int writers,
204 thread_context_t *contexts, rwlock_control_t *control,
205 summary_stats_t *summary, int config_idx, int lock_type) {
206 fprintf(stderr, "\n%-20s (readers: %d, writers: %d)\n", test_name, readers, writers);
207 fprintf(stderr, "%4s %8s %12s %12s %12s\n",
208 "THR", "TYPE", "OPS", "OPS/SEC", "TIME (ms)");
209
210 uint64_t total_ops = 0;
211 double total_ops_per_sec = 0;
212 double reader_ops_per_sec = 0;
213 double writer_ops_per_sec = 0;
214
215 for(int i = 0; i < readers + writers; i++) {
216 uint64_t ops = __atomic_load_n(&control->stats[i].operations, __ATOMIC_RELAXED);
217 usec_t time = __atomic_load_n(&control->stats[i].test_time, __ATOMIC_RELAXED);
218 double ops_per_sec = (double)ops * USEC_PER_SEC / time;
219
220 fprintf(stderr, "%4d %8s %12"PRIu64" %12.0f %12.2f\n",
221 i,
222 contexts[i].type == THREAD_READER ? "READER" : "WRITER",
223 ops,
224 ops_per_sec,
225 (double)time / 1000.0);
226
227 total_ops += ops;
228 total_ops_per_sec += ops_per_sec;
229
230 if (contexts[i].type == THREAD_READER) {
231 reader_ops_per_sec += ops_per_sec;
232 } else {
233 writer_ops_per_sec += ops_per_sec;
234 }
235 }
236
237 fprintf(stderr, "%4s %8s %12"PRIu64" %12.0f\n",
238 "TOT", "", total_ops, total_ops_per_sec);
239
240 // Store in summary
241 summary->ops_per_sec[lock_type][config_idx] = total_ops_per_sec;
242 summary->reader_ops_per_sec[lock_type][config_idx] = reader_ops_per_sec;
243 summary->writer_ops_per_sec[lock_type][config_idx] = writer_ops_per_sec;
244 summary->readers[config_idx] = readers;
245 summary->writers[config_idx] = writers;
246
247 verify_no_violations(control);
248 }
249
250
251 static void run_test(const char *name, int readers, int writers,
252 thread_context_t *contexts, rwlock_control_t *control,
253 summary_stats_t *summary, int config_idx, int lock_type) {
254 fprintf(stderr, "\nRunning test: %s with %d readers and %d writers...\n",
255 name, readers, writers);
256
257 // Reset all stats and control
258 memset(&control->stats, 0, sizeof(control->stats));
259 control->counter = 0;
260 control->readers = 0;
261 control->writers = 0;
262 control->violations = 0;
263
264 int total_threads = readers + writers;
265
266 // Signal threads to start
267 for(int i = 0; i < total_threads; i++) {
268 netdata_mutex_lock(&control->thread_controls[i].cond_mutex);
269 control->thread_controls[i].run_flag = 1;
270 netdata_cond_signal(&control->thread_controls[i].cond);
271 netdata_mutex_unlock(&control->thread_controls[i].cond_mutex);
272 }
273
274 // Wait for test duration
275 sleep_usec(TEST_DURATION_SEC * USEC_PER_SEC);
276
277 // Signal threads to stop
278 for(int i = 0; i < total_threads; i++) {
279 __atomic_store_n(&control->thread_controls[i].run_flag, 0, __ATOMIC_RELEASE);
280 }
281
282 // Wait for threads to report results
283 for(int i = 0; i < total_threads; i++) {
284 while(!__atomic_load_n(&control->stats[i].ready, __ATOMIC_ACQUIRE))
285 sleep_usec(10);
286 }
287
288 print_thread_stats(name, readers, writers, contexts, control, summary, config_idx, lock_type);
289 }
290
291 int rwlocks_stress_test(void) {
292 netdata_rwlock_t netdata_rwlock;
293 netdata_rwlock_init(&netdata_rwlock);
294
295 RW_SPINLOCK rw_spinlock = RW_SPINLOCK_INITIALIZER;
296 summary_stats_t summary = {0};
297
298 // Initialize control structures
299 rwlock_control_t netdata_control = { 0 };
300 rwlock_control_t spinlock_control = { 0 };
301
302 // Initialize per-thread controls for both locks
303 for(int i = 0; i < MAX_THREADS; i++) {
304 netdata_cond_init(&netdata_control.thread_controls[i].cond);
305 netdata_mutex_init(&netdata_control.thread_controls[i].cond_mutex);
306 netdata_control.thread_controls[i].run_flag = 0;
307
308 netdata_cond_init(&spinlock_control.thread_controls[i].cond);
309 netdata_mutex_init(&spinlock_control.thread_controls[i].cond_mutex);
310
311 spinlock_control.thread_controls[i].run_flag = 0;
312 }
313
314 // Create thread contexts
315 thread_context_t netdata_contexts[MAX_THREADS];
316 thread_context_t spinlock_contexts[MAX_THREADS];
317
318 fprintf(stderr, "\nStarting RW locks benchmark...\n");
319
320 // Test configurations: [readers, writers]
321 int configs[][2] = {
322 {1, 0}, // Single reader
323 {0, 1}, // Single writer
324 {1, 1}, // One reader + one writer
325 {2, 1}, // Two readers + one writer
326 {1, 2}, // One reader + two writers
327 {2, 2}, // Two readers + two writers
328 {4, 1}, // Four readers + one writer
329 {1, 4}, // One reader + four writers
330 {4, 4}, // Four readers + four writers
331 };
332
333 const int num_configs = sizeof(configs) / sizeof(configs[0]);
334 summary.config_count = num_configs;
335
336 // Create all threads
337 for(int i = 0; i < MAX_THREADS; i++) {
338 char thr_name[32];
339
340 // Initialize pthread contexts
341 netdata_contexts[i] = (thread_context_t){
342 .thread_id = i,
343 .type = i % 2 == 0 ? THREAD_READER :THREAD_WRITER,
344 .lock = &netdata_rwlock,
345 .is_spinlock = false,
346 .control = &netdata_control
347 };
348
349 snprintf(thr_name, sizeof(thr_name), "netdata_rw%d", i);
350 netdata_contexts[i].thread =
351 nd_thread_create(thr_name, NETDATA_THREAD_OPTION_DONT_LOG, benchmark_thread, &netdata_contexts[i]);
352
353 // Initialize spinlock contexts
354 spinlock_contexts[i] = (thread_context_t){
355 .thread_id = i,
356 .type = i % 2 == 0 ? THREAD_READER : THREAD_WRITER,
357 .lock = &rw_spinlock,
358 .is_spinlock = true,
359 .control = &spinlock_control
360 };
361
362 snprintf(thr_name, sizeof(thr_name), "spin_rw%d", i);
363 spinlock_contexts[i].thread =
364 nd_thread_create(thr_name, NETDATA_THREAD_OPTION_DONT_LOG, benchmark_thread, &spinlock_contexts[i]);
365 }
366
367 // Run all configurations
368 for(int i = 0; i < num_configs; i++) {
369 int readers = configs[i][0];
370 int writers = configs[i][1];
371
372 // Create all threads
373 int thread_idx = 0;
374
375 // First assign reader threads
376 for(int r = 0; r < readers; r++) {
377 netdata_contexts[thread_idx].type = THREAD_READER;
378 spinlock_contexts[thread_idx].type = THREAD_READER;
379 thread_idx++;
380 }
381
382 // Then assign writer threads
383 for(int w = 0; w < writers; w++) {
384 netdata_contexts[thread_idx].type = THREAD_WRITER;
385 spinlock_contexts[thread_idx].type = THREAD_WRITER;
386 thread_idx++;
387 }
388
389 char test_name[64];
390 snprintf(test_name, sizeof(test_name), "netdata_rwlock %dR/%dW", readers, writers);
391 run_test(test_name, readers, writers, netdata_contexts, &netdata_control, &summary, i, 0);
392
393 snprintf(test_name, sizeof(test_name), "rw_spinlock %dR/%dW", readers, writers);
394 run_test(test_name, readers, writers, spinlock_contexts, &spinlock_control, &summary, i, 1);
395 }
396
397 // Print the summary table
398 print_summary(&summary);
399
400 // Stop all threads
401 fprintf(stderr, "\nStopping threads...\n");
402 for(int i = 0; i < MAX_THREADS; i++) {
403 // Signal pthread threads
404 netdata_mutex_lock(&netdata_control.thread_controls[i].cond_mutex);
405 netdata_control.thread_controls[i].run_flag = STOP_SIGNAL;
406 netdata_cond_signal(&netdata_control.thread_controls[i].cond);
407 netdata_mutex_unlock(&netdata_control.thread_controls[i].cond_mutex);
408
409 // Signal spinlock threads
410 netdata_mutex_lock(&spinlock_control.thread_controls[i].cond_mutex);
411 spinlock_control.thread_controls[i].run_flag = STOP_SIGNAL;
412 netdata_cond_signal(&spinlock_control.thread_controls[i].cond);
413 netdata_mutex_unlock(&spinlock_control.thread_controls[i].cond_mutex);
414 }
415
416 // Join all threads
417 fprintf(stderr, "\nWaiting for threads to exit...\n");
418 for(int i = 0; i < MAX_THREADS; i++) {
419 nd_thread_join(netdata_contexts[i].thread);
420 nd_thread_join(spinlock_contexts[i].thread);
421 }
422
423 // Cleanup condition variables and mutexes
424 for(int i = 0; i < MAX_THREADS; i++) {
425 netdata_cond_destroy(&netdata_control.thread_controls[i].cond);
426 netdata_mutex_destroy(&netdata_control.thread_controls[i].cond_mutex);
427 netdata_cond_destroy(&spinlock_control.thread_controls[i].cond);
428 netdata_mutex_destroy(&spinlock_control.thread_controls[i].cond_mutex);
429 }
430
431 netdata_rwlock_destroy(&netdata_rwlock);
432
433 return 0;
434 }