Line data Source code
1 : #define _GNU_SOURCE
2 :
3 : #include "fd_accdb.h"
4 : #include "../../util/fd_util.h"
5 :
6 : #include <stdlib.h>
7 : #include <string.h>
8 : #include <unistd.h>
9 : #include <sys/mman.h>
10 : #include <pthread.h>
11 : #include <sched.h>
12 : #include <time.h>
13 :
14 : /* Measures only the T2 command body for root advancement and fork
15 : purge. T1 prepares one zero-data account version per pubkey before
16 : submitting the command. Reusing the same pubkeys each slot forces
17 : root cleanup to find superseded versions while keeping account data
18 : size out of this metadata-focused benchmark.
19 :
20 : Pin T1 and T2 to separate physical cores for repeatable results:
21 :
22 : bench_accdb_cleanup --t1-cpu 94 --t2-cpu 95 */
23 :
24 0 : #define BENCH_CACHE_FOOTPRINT (64UL<<20UL)
25 0 : #define BENCH_CACHE_MIN_RESERVED (2UL)
26 0 : #define BENCH_MAX_LIVE_SLOTS (8UL)
27 0 : #define BENCH_PARTITION_CNT (64UL)
28 0 : #define BENCH_PARTITION_SZ (1UL<<30UL)
29 :
30 : typedef struct {
31 : fd_accdb_t * t1;
32 : fd_accdb_t * t2;
33 : void * t1_mem;
34 : void * t2_mem;
35 : void * shmem_mem;
36 : int fd;
37 : } bench_env_t;
38 :
39 : static bench_env_t
40 0 : bench_setup( ulong writes_per_slot ) {
41 0 : FD_TEST( writes_per_slot );
42 0 : FD_TEST( writes_per_slot<=(UINT_MAX-1024UL)/5UL );
43 0 : FD_TEST( writes_per_slot<=UINT_MAX/BENCH_MAX_LIVE_SLOTS-16UL );
44 :
45 0 : ulong max_accounts = 5UL*writes_per_slot + 1024UL;
46 0 : ulong max_writes = writes_per_slot + 16UL;
47 :
48 0 : int fd = memfd_create( "accdb_cleanup_bench", 0 );
49 0 : if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "memfd_create failed" ));
50 :
51 0 : ulong shmem_fp = fd_accdb_shmem_footprint( max_accounts,
52 0 : BENCH_MAX_LIVE_SLOTS,
53 0 : max_writes,
54 0 : BENCH_PARTITION_CNT,
55 0 : BENCH_CACHE_FOOTPRINT,
56 0 : BENCH_CACHE_MIN_RESERVED,
57 0 : 2UL,
58 0 : 0UL );
59 0 : FD_TEST( shmem_fp );
60 0 : void * shmem_mem = aligned_alloc( fd_accdb_shmem_align(), shmem_fp );
61 0 : FD_TEST( shmem_mem );
62 0 : fd_accdb_shmem_t * shmem = fd_accdb_shmem_join(
63 0 : fd_accdb_shmem_new( shmem_mem,
64 0 : max_accounts,
65 0 : BENCH_MAX_LIVE_SLOTS,
66 0 : max_writes,
67 0 : BENCH_PARTITION_CNT,
68 0 : BENCH_PARTITION_SZ,
69 0 : BENCH_CACHE_FOOTPRINT,
70 0 : BENCH_CACHE_MIN_RESERVED,
71 0 : 0,
72 0 : 42UL,
73 0 : 2UL,
74 0 : 0UL ) );
75 0 : FD_TEST( shmem );
76 :
77 0 : ulong accdb_fp = fd_accdb_footprint( BENCH_MAX_LIVE_SLOTS );
78 0 : FD_TEST( accdb_fp );
79 0 : void * t1_mem = aligned_alloc( fd_accdb_align(), accdb_fp );
80 0 : void * t2_mem = aligned_alloc( fd_accdb_align(), accdb_fp );
81 0 : FD_TEST( t1_mem );
82 0 : FD_TEST( t2_mem );
83 0 : fd_accdb_t * t1 = fd_accdb_join( fd_accdb_new( t1_mem, shmem, fd, 0UL, NULL ) );
84 0 : fd_accdb_t * t2 = fd_accdb_join( fd_accdb_new( t2_mem, shmem, fd, 0UL, NULL ) );
85 0 : FD_TEST( t1 );
86 0 : FD_TEST( t2 );
87 :
88 0 : return (bench_env_t){
89 0 : .t1 = t1,
90 0 : .t2 = t2,
91 0 : .t1_mem = t1_mem,
92 0 : .t2_mem = t2_mem,
93 0 : .shmem_mem = shmem_mem,
94 0 : .fd = fd
95 0 : };
96 0 : }
97 :
98 : static void
99 0 : bench_teardown( bench_env_t * env ) {
100 0 : free( env->shmem_mem );
101 0 : free( env->t1_mem );
102 0 : free( env->t2_mem );
103 0 : close( env->fd );
104 0 : }
105 :
106 : static void
107 0 : pin_thread( ulong cpu ) {
108 0 : if( cpu==ULONG_MAX ) return;
109 0 : FD_TEST( cpu<CPU_SETSIZE );
110 0 : cpu_set_t set;
111 0 : CPU_ZERO( &set );
112 0 : CPU_SET( cpu, &set );
113 0 : FD_TEST( !pthread_setaffinity_np( pthread_self(), sizeof(set), &set ) );
114 0 : }
115 :
116 : static ulong
117 0 : monotonic_raw( void ) {
118 0 : struct timespec ts;
119 0 : FD_TEST( !clock_gettime( CLOCK_MONOTONIC_RAW, &ts ) );
120 0 : return (ulong)ts.tv_sec*1000000000UL + (ulong)ts.tv_nsec;
121 0 : }
122 :
123 : typedef struct {
124 : fd_accdb_t * accdb;
125 : pthread_t thread;
126 : pthread_mutex_t lock;
127 : pthread_cond_t cond;
128 : ulong cpu;
129 : ulong elapsed;
130 : int request;
131 : int done;
132 : int stop;
133 : } background_worker_t;
134 :
135 : static void *
136 0 : background_main( void * _worker ) {
137 0 : background_worker_t * worker = _worker;
138 0 : pin_thread( worker->cpu );
139 :
140 0 : FD_TEST( !pthread_mutex_lock( &worker->lock ) );
141 0 : for(;;) {
142 0 : while( !worker->request && !worker->stop )
143 0 : FD_TEST( !pthread_cond_wait( &worker->cond, &worker->lock ) );
144 0 : if( worker->stop ) break;
145 0 : worker->request = 0;
146 0 : FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
147 :
148 0 : int charge_busy = 0;
149 0 : ulong then = monotonic_raw();
150 0 : fd_accdb_background( worker->accdb, &charge_busy );
151 0 : ulong now = monotonic_raw();
152 0 : FD_TEST( charge_busy );
153 :
154 0 : FD_TEST( !pthread_mutex_lock( &worker->lock ) );
155 0 : worker->elapsed = now-then;
156 0 : worker->done = 1;
157 0 : FD_TEST( !pthread_cond_signal( &worker->cond ) );
158 0 : }
159 0 : FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
160 0 : return NULL;
161 0 : }
162 :
163 : static void
164 : background_start( background_worker_t * worker,
165 : fd_accdb_t * accdb,
166 0 : ulong cpu ) {
167 0 : *worker = (background_worker_t){
168 0 : .accdb = accdb,
169 0 : .cpu = cpu
170 0 : };
171 0 : FD_TEST( !pthread_mutex_init( &worker->lock, NULL ) );
172 0 : FD_TEST( !pthread_cond_init( &worker->cond, NULL ) );
173 0 : FD_TEST( !pthread_create( &worker->thread, NULL, background_main, worker ) );
174 0 : }
175 :
176 : static ulong
177 0 : background_run( background_worker_t * worker ) {
178 0 : FD_TEST( !pthread_mutex_lock( &worker->lock ) );
179 0 : FD_TEST( !worker->request );
180 0 : FD_TEST( !worker->done );
181 0 : worker->request = 1;
182 0 : FD_TEST( !pthread_cond_signal( &worker->cond ) );
183 0 : while( !worker->done )
184 0 : FD_TEST( !pthread_cond_wait( &worker->cond, &worker->lock ) );
185 0 : ulong elapsed = worker->elapsed;
186 0 : worker->done = 0;
187 0 : FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
188 0 : return elapsed;
189 0 : }
190 :
191 : static void
192 0 : background_stop( background_worker_t * worker ) {
193 0 : FD_TEST( !pthread_mutex_lock( &worker->lock ) );
194 0 : worker->stop = 1;
195 0 : FD_TEST( !pthread_cond_signal( &worker->cond ) );
196 0 : FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
197 0 : FD_TEST( !pthread_join( worker->thread, NULL ) );
198 0 : FD_TEST( !pthread_cond_destroy( &worker->cond ) );
199 0 : FD_TEST( !pthread_mutex_destroy( &worker->lock ) );
200 0 : }
201 :
202 : static void
203 : make_pubkey( uchar pubkey[ static 32 ],
204 0 : ulong idx ) {
205 0 : fd_memset( pubkey, 0, 32UL );
206 0 : fd_memcpy( pubkey, &idx, sizeof(ulong) );
207 0 : }
208 :
209 : static void
210 : write_slot( fd_accdb_t * accdb,
211 : fd_accdb_fork_id_t fork_id,
212 : ulong writes_per_slot,
213 0 : ulong slot ) {
214 0 : for( ulong i=0UL; i<writes_per_slot; i++ ) {
215 0 : uchar pubkey[ 32 ];
216 0 : make_pubkey( pubkey, i );
217 :
218 0 : uchar const * pubkeys[ 1 ] = { pubkey };
219 0 : int writable[ 1 ] = { 1 };
220 0 : fd_acc_t acc[ 1 ];
221 0 : fd_memset( acc, 0, sizeof(acc) );
222 0 : fd_accdb_acquire( accdb, fork_id, 1UL, pubkeys, writable, acc );
223 0 : acc[ 0 ].lamports = slot + 1UL;
224 0 : acc[ 0 ].data_len = 0UL;
225 0 : fd_memset( acc[ 0 ].owner, 0, sizeof(acc[ 0 ].owner) );
226 0 : acc[ 0 ].owner[ 0 ] = 1U;
227 0 : acc[ 0 ].commit = 1;
228 0 : fd_accdb_release( accdb, 1UL, acc );
229 0 : }
230 0 : }
231 :
232 : static int
233 : cmp_ulong( void const * _a,
234 0 : void const * _b ) {
235 0 : ulong a = *(ulong const *)_a;
236 0 : ulong b = *(ulong const *)_b;
237 0 : return (a>b) - (a<b);
238 0 : }
239 :
240 : static void
241 : report( char const * mode,
242 : ulong writes_per_slot,
243 : ulong warmup_cnt,
244 : ulong sample_cnt,
245 0 : ulong * samples ) {
246 0 : double sum = 0.0;
247 0 : for( ulong i=0UL; i<sample_cnt; i++ ) sum += (double)samples[ i ];
248 0 : qsort( samples, sample_cnt, sizeof(ulong), cmp_ulong );
249 :
250 0 : ulong rank50 = (sample_cnt/100UL)*50UL + ((sample_cnt%100UL)*50UL+99UL)/100UL;
251 0 : ulong rank95 = (sample_cnt/100UL)*95UL + ((sample_cnt%100UL)*95UL+99UL)/100UL;
252 0 : ulong p50 = samples[ rank50-1UL ];
253 0 : ulong p95 = samples[ rank95-1UL ];
254 0 : double mean = sum/(double)sample_cnt;
255 :
256 0 : FD_LOG_NOTICE(( "%s: writes/slot=%lu warmup=%lu samples=%lu", mode, writes_per_slot, warmup_cnt, sample_cnt ));
257 0 : FD_LOG_NOTICE(( " mean %.0f ns p50 %lu ns p95 %lu ns", mean, p50, p95 ));
258 0 : FD_LOG_NOTICE(( " mean %.2f ns/write p50 %.2f ns/write p95 %.2f ns/write",
259 0 : mean/(double)writes_per_slot,
260 0 : (double)p50/(double)writes_per_slot,
261 0 : (double)p95/(double)writes_per_slot ));
262 0 : }
263 :
264 : static void
265 : bench_root( ulong writes_per_slot,
266 : ulong warmup_cnt,
267 : ulong sample_cnt,
268 0 : ulong t2_cpu ) {
269 0 : bench_env_t env = bench_setup( writes_per_slot );
270 0 : ulong * samples = malloc( sample_cnt*sizeof(ulong) );
271 0 : FD_TEST( samples );
272 0 : background_worker_t worker[ 1 ];
273 0 : background_start( worker, env.t2, t2_cpu );
274 :
275 0 : fd_accdb_fork_id_t root = fd_accdb_attach_child(
276 0 : env.t1, (fd_accdb_fork_id_t){ .val = USHORT_MAX } );
277 0 : write_slot( env.t1, root, writes_per_slot, 0UL );
278 :
279 0 : fd_accdb_fork_id_t prev = fd_accdb_attach_child( env.t1, root );
280 0 : write_slot( env.t1, prev, writes_per_slot, 1UL );
281 :
282 : /* Prime the old-root and deferred-free paths before any samples. */
283 0 : fd_accdb_fork_id_t cur = fd_accdb_attach_child( env.t1, prev );
284 0 : write_slot( env.t1, cur, writes_per_slot, 2UL );
285 0 : fd_accdb_advance_root( env.t1, prev );
286 0 : (void)background_run( worker );
287 0 : prev = cur;
288 :
289 0 : ulong iter_cnt = warmup_cnt + sample_cnt;
290 0 : for( ulong iter=0UL; iter<iter_cnt; iter++ ) {
291 0 : cur = fd_accdb_attach_child( env.t1, prev );
292 0 : write_slot( env.t1, cur, writes_per_slot, iter+3UL );
293 :
294 0 : fd_accdb_advance_root( env.t1, prev );
295 0 : ulong elapsed = background_run( worker );
296 0 : if( iter>=warmup_cnt ) samples[ iter-warmup_cnt ] = elapsed;
297 0 : prev = cur;
298 0 : }
299 :
300 0 : report( "root", writes_per_slot, warmup_cnt, sample_cnt, samples );
301 0 : background_stop( worker );
302 0 : free( samples );
303 0 : bench_teardown( &env );
304 0 : }
305 :
306 : static void
307 : bench_purge( ulong writes_per_slot,
308 : ulong warmup_cnt,
309 : ulong sample_cnt,
310 0 : ulong t2_cpu ) {
311 0 : bench_env_t env = bench_setup( writes_per_slot );
312 0 : ulong * samples = malloc( sample_cnt*sizeof(ulong) );
313 0 : FD_TEST( samples );
314 0 : background_worker_t worker[ 1 ];
315 0 : background_start( worker, env.t2, t2_cpu );
316 :
317 0 : fd_accdb_fork_id_t root = fd_accdb_attach_child(
318 0 : env.t1, (fd_accdb_fork_id_t){ .val = USHORT_MAX } );
319 0 : write_slot( env.t1, root, writes_per_slot, 0UL );
320 :
321 : /* Prime deferred reclamation before any samples. */
322 0 : fd_accdb_fork_id_t fork = fd_accdb_attach_child( env.t1, root );
323 0 : write_slot( env.t1, fork, writes_per_slot, 1UL );
324 0 : fd_accdb_purge( env.t1, fork );
325 0 : (void)background_run( worker );
326 :
327 0 : ulong iter_cnt = warmup_cnt + sample_cnt;
328 0 : for( ulong iter=0UL; iter<iter_cnt; iter++ ) {
329 0 : fork = fd_accdb_attach_child( env.t1, root );
330 0 : write_slot( env.t1, fork, writes_per_slot, iter+2UL );
331 :
332 0 : fd_accdb_purge( env.t1, fork );
333 0 : ulong elapsed = background_run( worker );
334 0 : if( iter>=warmup_cnt ) samples[ iter-warmup_cnt ] = elapsed;
335 0 : }
336 :
337 0 : report( "purge", writes_per_slot, warmup_cnt, sample_cnt, samples );
338 0 : background_stop( worker );
339 0 : free( samples );
340 0 : bench_teardown( &env );
341 0 : }
342 :
343 : int
344 : main( int argc,
345 : char ** argv ) {
346 : fd_boot( &argc, &argv );
347 :
348 : char const * mode = fd_env_strip_cmdline_cstr( &argc, &argv, "--mode", NULL, "both" );
349 : ulong writes_per_slot = fd_env_strip_cmdline_ulong( &argc, &argv, "--writes-per-slot", NULL, 10000UL );
350 : ulong warmup_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--warmup", NULL, 10UL );
351 : ulong sample_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--samples", NULL, 50UL );
352 : ulong t1_cpu = fd_env_strip_cmdline_ulong( &argc, &argv, "--t1-cpu", NULL, ULONG_MAX );
353 : ulong t2_cpu = fd_env_strip_cmdline_ulong( &argc, &argv, "--t2-cpu", NULL, ULONG_MAX );
354 :
355 : FD_TEST( writes_per_slot );
356 : FD_TEST( sample_cnt );
357 : FD_TEST( sample_cnt<=ULONG_MAX/sizeof(ulong) );
358 : FD_TEST( warmup_cnt<=ULONG_MAX-sample_cnt );
359 : pin_thread( t1_cpu );
360 :
361 : if( !strcmp( mode, "root" ) ) {
362 : bench_root( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
363 : } else if( !strcmp( mode, "purge" ) ) {
364 : bench_purge( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
365 : } else if( !strcmp( mode, "both" ) ) {
366 : bench_root ( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
367 : bench_purge( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
368 : } else {
369 : FD_LOG_ERR(( "unsupported --mode %s (expected root, purge, or both)", mode ));
370 : }
371 :
372 : FD_LOG_NOTICE(( "pass" ));
373 : fd_halt();
374 : return 0;
375 : }
|