Mercurial > sdl-ios-xcode
comparison test/testatomic.c @ 5100:470ede30189c
Added a FIFO test to the atomic test suite.
This is really useful because we might be able to use something like this
for the SDL event queue.
author | Sam Lantinga <slouken@libsdl.org> |
---|---|
date | Tue, 25 Jan 2011 23:23:52 -0800 |
parents | 342b158efbbe |
children | 1b3678ac9804 |
comparison
equal
deleted
inserted
replaced
5099:b938ad843e52 | 5100:470ede30189c |
---|---|
229 } | 229 } |
230 | 230 |
231 /* End atomic operation test */ | 231 /* End atomic operation test */ |
232 /**************************************************************************/ | 232 /**************************************************************************/ |
233 | 233 |
234 /**************************************************************************/ | |
235 /* Lock-free FIFO test */ | |
236 | |
237 #define NUM_READERS 4 | |
238 #define NUM_WRITERS 4 | |
239 #define EVENTS_PER_WRITER 1000000 | |
240 | |
241 /* A decent guess for the size of a cache line on this architecture */ | |
242 #define CACHELINE 64 | |
243 | |
244 /* The number of entries must be a power of 2 */ | |
245 #define MAX_ENTRIES 256 | |
246 #define WRAP_MASK (MAX_ENTRIES-1) | |
247 | |
248 typedef struct | |
249 { | |
250 SDL_atomic_t sequence; | |
251 SDL_Event event; | |
252 } SDL_EventQueueEntry; | |
253 | |
254 typedef struct | |
255 { | |
256 SDL_EventQueueEntry entries[MAX_ENTRIES]; | |
257 | |
258 char cache_pad1[CACHELINE-((sizeof(SDL_EventQueueEntry)*MAX_ENTRIES)%CACHELINE)]; | |
259 | |
260 SDL_atomic_t enqueue_pos; | |
261 | |
262 char cache_pad2[CACHELINE-sizeof(SDL_atomic_t)]; | |
263 | |
264 SDL_atomic_t dequeue_pos; | |
265 | |
266 char cache_pad3[CACHELINE-sizeof(SDL_atomic_t)]; | |
267 | |
268 SDL_bool active; | |
269 | |
270 /* Only needed for the mutex test */ | |
271 SDL_mutex *mutex; | |
272 | |
273 } SDL_EventQueue; | |
274 | |
275 static void InitEventQueue(SDL_EventQueue *queue) | |
276 { | |
277 int i; | |
278 | |
279 for (i = 0; i < MAX_ENTRIES; ++i) { | |
280 SDL_AtomicSet(&queue->entries[i].sequence, i); | |
281 } | |
282 SDL_AtomicSet(&queue->enqueue_pos, 0); | |
283 SDL_AtomicSet(&queue->dequeue_pos, 0); | |
284 queue->active = SDL_TRUE; | |
285 } | |
286 | |
287 static SDL_bool EnqueueEvent_LockFree(SDL_EventQueue *queue, const SDL_Event *event) | |
288 { | |
289 SDL_EventQueueEntry *entry; | |
290 unsigned queue_pos; | |
291 unsigned entry_seq; | |
292 int delta; | |
293 | |
294 queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos); | |
295 for ( ; ; ) { | |
296 entry = &queue->entries[queue_pos & WRAP_MASK]; | |
297 entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence); | |
298 | |
299 delta = (int)(entry_seq - queue_pos); | |
300 if (delta == 0) { | |
301 /* The entry and the queue position match, try to increment the queue position */ | |
302 if (SDL_AtomicCAS(&queue->enqueue_pos, (int)queue_pos, (int)(queue_pos+1))) { | |
303 break; | |
304 } | |
305 } else if (delta < 0) { | |
306 /* We ran into an old queue entry, which means it still needs to be dequeued */ | |
307 return SDL_FALSE; | |
308 } else { | |
309 /* We ran into a new queue entry, get the new queue position */ | |
310 queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos); | |
311 } | |
312 } | |
313 | |
314 /* We own the object, fill it! */ | |
315 entry->event = *event; | |
316 SDL_AtomicSet(&entry->sequence, (int)(queue_pos + 1)); | |
317 | |
318 return SDL_TRUE; | |
319 } | |
320 | |
321 static SDL_bool DequeueEvent_LockFree(SDL_EventQueue *queue, SDL_Event *event) | |
322 { | |
323 SDL_EventQueueEntry *entry; | |
324 unsigned queue_pos; | |
325 unsigned entry_seq; | |
326 int delta; | |
327 | |
328 queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos); | |
329 for ( ; ; ) { | |
330 entry = &queue->entries[queue_pos & WRAP_MASK]; | |
331 entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence); | |
332 | |
333 delta = (int)(entry_seq - (queue_pos + 1)); | |
334 if (delta == 0) { | |
335 /* The entry and the queue position match, try to increment the queue position */ | |
336 if (SDL_AtomicCAS(&queue->dequeue_pos, (int)queue_pos, (int)(queue_pos+1))) { | |
337 break; | |
338 } | |
339 } else if (delta < 0) { | |
340 /* We ran into an old queue entry, which means we've hit empty */ | |
341 return SDL_FALSE; | |
342 } else { | |
343 /* We ran into a new queue entry, get the new queue position */ | |
344 queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos); | |
345 } | |
346 } | |
347 | |
348 /* We own the object, fill it! */ | |
349 *event = entry->event; | |
350 SDL_AtomicSet(&entry->sequence, (int)(queue_pos+MAX_ENTRIES)); | |
351 | |
352 return SDL_TRUE; | |
353 } | |
354 | |
355 static SDL_bool EnqueueEvent_Mutex(SDL_EventQueue *queue, const SDL_Event *event) | |
356 { | |
357 SDL_EventQueueEntry *entry; | |
358 unsigned queue_pos; | |
359 unsigned entry_seq; | |
360 int delta; | |
361 | |
362 SDL_mutexP(queue->mutex); | |
363 | |
364 queue_pos = (unsigned)queue->enqueue_pos.value; | |
365 entry = &queue->entries[queue_pos & WRAP_MASK]; | |
366 entry_seq = (unsigned)entry->sequence.value; | |
367 | |
368 delta = (int)(entry_seq - queue_pos); | |
369 if (delta == 0) { | |
370 ++queue->enqueue_pos.value; | |
371 } else if (delta < 0) { | |
372 /* We ran into an old queue entry, which means it still needs to be dequeued */ | |
373 SDL_mutexV(queue->mutex); | |
374 return SDL_FALSE; | |
375 } else { | |
376 printf("ERROR: mutex failed!\n"); | |
377 } | |
378 | |
379 /* We own the object, fill it! */ | |
380 entry->event = *event; | |
381 entry->sequence.value = (int)(queue_pos + 1); | |
382 | |
383 SDL_mutexV(queue->mutex); | |
384 | |
385 return SDL_TRUE; | |
386 } | |
387 | |
388 static SDL_bool DequeueEvent_Mutex(SDL_EventQueue *queue, SDL_Event *event) | |
389 { | |
390 SDL_EventQueueEntry *entry; | |
391 unsigned queue_pos; | |
392 unsigned entry_seq; | |
393 int delta; | |
394 | |
395 SDL_mutexP(queue->mutex); | |
396 | |
397 queue_pos = (unsigned)queue->dequeue_pos.value; | |
398 entry = &queue->entries[queue_pos & WRAP_MASK]; | |
399 entry_seq = (unsigned)entry->sequence.value; | |
400 | |
401 delta = (int)(entry_seq - (queue_pos + 1)); | |
402 if (delta == 0) { | |
403 ++queue->dequeue_pos.value; | |
404 } else if (delta < 0) { | |
405 /* We ran into an old queue entry, which means we've hit empty */ | |
406 SDL_mutexV(queue->mutex); | |
407 return SDL_FALSE; | |
408 } else { | |
409 printf("ERROR: mutex failed!\n"); | |
410 } | |
411 | |
412 /* We own the object, fill it! */ | |
413 *event = entry->event; | |
414 entry->sequence.value = (int)(queue_pos + MAX_ENTRIES); | |
415 | |
416 SDL_mutexV(queue->mutex); | |
417 | |
418 return SDL_TRUE; | |
419 } | |
420 | |
421 static SDL_sem *writersDone; | |
422 static SDL_sem *readersDone; | |
423 static SDL_atomic_t writersRunning; | |
424 static SDL_atomic_t readersRunning; | |
425 | |
426 typedef struct | |
427 { | |
428 SDL_EventQueue *queue; | |
429 int index; | |
430 char padding1[CACHELINE-(sizeof(SDL_EventQueue*)+sizeof(int))%CACHELINE]; | |
431 int waits; | |
432 SDL_bool lock_free; | |
433 char padding2[CACHELINE-sizeof(int)-sizeof(SDL_bool)]; | |
434 } WriterData; | |
435 | |
436 typedef struct | |
437 { | |
438 SDL_EventQueue *queue; | |
439 int counters[NUM_WRITERS]; | |
440 int waits; | |
441 SDL_bool lock_free; | |
442 char padding[CACHELINE-(sizeof(SDL_EventQueue*)+sizeof(int)*NUM_WRITERS+sizeof(int)+sizeof(SDL_bool))%CACHELINE]; | |
443 } ReaderData; | |
444 | |
445 static int FIFO_Writer(void* _data) | |
446 { | |
447 WriterData *data = (WriterData *)_data; | |
448 SDL_EventQueue *queue = data->queue; | |
449 int index = data->index; | |
450 int i; | |
451 SDL_Event event; | |
452 | |
453 event.type = SDL_USEREVENT; | |
454 event.user.windowID = 0; | |
455 event.user.code = 0; | |
456 event.user.data1 = data; | |
457 event.user.data2 = NULL; | |
458 | |
459 if (data->lock_free) { | |
460 for (i = 0; i < EVENTS_PER_WRITER; ++i) { | |
461 event.user.code = i; | |
462 while (!EnqueueEvent_LockFree(queue, &event)) { | |
463 ++data->waits; | |
464 SDL_Delay(0); | |
465 } | |
466 } | |
467 } else { | |
468 for (i = 0; i < EVENTS_PER_WRITER; ++i) { | |
469 event.user.code = i; | |
470 while (!EnqueueEvent_Mutex(queue, &event)) { | |
471 ++data->waits; | |
472 SDL_Delay(0); | |
473 } | |
474 } | |
475 } | |
476 SDL_AtomicAdd(&writersRunning, -1); | |
477 SDL_SemPost(writersDone); | |
478 return 0; | |
479 } | |
480 | |
481 static int FIFO_Reader(void* _data) | |
482 { | |
483 ReaderData *data = (ReaderData *)_data; | |
484 SDL_EventQueue *queue = data->queue; | |
485 SDL_Event event; | |
486 int index; | |
487 | |
488 if (data->lock_free) { | |
489 for ( ; ; ) { | |
490 if (DequeueEvent_LockFree(queue, &event)) { | |
491 WriterData *writer = (WriterData*)event.user.data1; | |
492 ++data->counters[writer->index]; | |
493 } else if (queue->active) { | |
494 ++data->waits; | |
495 SDL_Delay(0); | |
496 } else { | |
497 /* We drained the queue, we're done! */ | |
498 break; | |
499 } | |
500 } | |
501 } else { | |
502 for ( ; ; ) { | |
503 if (DequeueEvent_Mutex(queue, &event)) { | |
504 WriterData *writer = (WriterData*)event.user.data1; | |
505 ++data->counters[writer->index]; | |
506 } else if (queue->active) { | |
507 ++data->waits; | |
508 SDL_Delay(0); | |
509 } else { | |
510 /* We drained the queue, we're done! */ | |
511 break; | |
512 } | |
513 } | |
514 } | |
515 SDL_AtomicAdd(&readersRunning, -1); | |
516 SDL_SemPost(readersDone); | |
517 return 0; | |
518 } | |
519 | |
520 static void RunFIFOTest(SDL_bool lock_free) | |
521 { | |
522 SDL_EventQueue queue; | |
523 WriterData writerData[NUM_WRITERS]; | |
524 ReaderData readerData[NUM_READERS]; | |
525 Uint32 start, end; | |
526 int i, j; | |
527 int grand_total; | |
528 | |
529 printf("\nFIFO test---------------------------------------\n\n"); | |
530 printf("Mode: %s\n", lock_free ? "LockFree" : "Mutex"); | |
531 | |
532 readersDone = SDL_CreateSemaphore(0); | |
533 writersDone = SDL_CreateSemaphore(0); | |
534 | |
535 SDL_memset(&queue, 0xff, sizeof(queue)); | |
536 | |
537 InitEventQueue(&queue); | |
538 if (!lock_free) { | |
539 queue.mutex = SDL_CreateMutex(); | |
540 } | |
541 | |
542 start = SDL_GetTicks(); | |
543 | |
544 /* Start the readers first */ | |
545 printf("Starting %d readers\n", NUM_READERS); | |
546 SDL_zero(readerData); | |
547 SDL_AtomicSet(&readersRunning, NUM_READERS); | |
548 for (i = 0; i < NUM_READERS; ++i) { | |
549 readerData[i].queue = &queue; | |
550 readerData[i].lock_free = lock_free; | |
551 SDL_CreateThread(FIFO_Reader, &readerData[i]); | |
552 } | |
553 | |
554 /* Start up the writers */ | |
555 printf("Starting %d writers\n", NUM_WRITERS); | |
556 SDL_zero(writerData); | |
557 SDL_AtomicSet(&writersRunning, NUM_WRITERS); | |
558 for (i = 0; i < NUM_WRITERS; ++i) { | |
559 writerData[i].queue = &queue; | |
560 writerData[i].index = i; | |
561 writerData[i].lock_free = lock_free; | |
562 SDL_CreateThread(FIFO_Writer, &writerData[i]); | |
563 } | |
564 | |
565 /* Wait for the writers */ | |
566 while (SDL_AtomicGet(&writersRunning) > 0) { | |
567 SDL_SemWait(writersDone); | |
568 } | |
569 | |
570 /* Shut down the queue so readers exit */ | |
571 queue.active = SDL_FALSE; | |
572 | |
573 /* Wait for the readers */ | |
574 while (SDL_AtomicGet(&readersRunning) > 0) { | |
575 SDL_SemWait(readersDone); | |
576 } | |
577 | |
578 end = SDL_GetTicks(); | |
579 | |
580 SDL_DestroySemaphore(readersDone); | |
581 SDL_DestroySemaphore(writersDone); | |
582 | |
583 if (!lock_free) { | |
584 SDL_DestroyMutex(queue.mutex); | |
585 } | |
586 | |
587 printf("Finished in %f sec\n", (end - start) / 1000.f); | |
588 | |
589 printf("\n"); | |
590 for (i = 0; i < NUM_WRITERS; ++i) { | |
591 printf("Writer %d wrote %d events, had %d waits\n", i, EVENTS_PER_WRITER, writerData[i].waits); | |
592 } | |
593 printf("Writers wrote %d total events\n", NUM_WRITERS*EVENTS_PER_WRITER); | |
594 | |
595 /* Print a breakdown of which readers read messages from which writer */ | |
596 printf("\n"); | |
597 grand_total = 0; | |
598 for (i = 0; i < NUM_READERS; ++i) { | |
599 int total = 0; | |
600 for (j = 0; j < NUM_WRITERS; ++j) { | |
601 total += readerData[i].counters[j]; | |
602 } | |
603 grand_total += total; | |
604 printf("Reader %d read %d events, had %d waits\n", i, total, readerData[i].waits); | |
605 printf(" { "); | |
606 for (j = 0; j < NUM_WRITERS; ++j) { | |
607 if (j > 0) { | |
608 printf(", "); | |
609 } | |
610 printf("%d", readerData[i].counters[j]); | |
611 } | |
612 printf(" }\n"); | |
613 } | |
614 printf("Readers read %d total events\n", grand_total); | |
615 } | |
616 | |
617 /* End FIFO test */ | |
618 /**************************************************************************/ | |
619 | |
234 int | 620 int |
235 main(int argc, char *argv[]) | 621 main(int argc, char *argv[]) |
236 { | 622 { |
237 RunBasicTest(); | 623 RunBasicTest(); |
238 RunEpicTest(); | 624 RunEpicTest(); |
625 /* This test is really slow, so don't run it by default */ | |
626 #if 0 | |
627 RunFIFOTest(SDL_FALSE); | |
628 #endif | |
629 RunFIFOTest(SDL_TRUE); | |
239 return 0; | 630 return 0; |
240 } | 631 } |
632 | |
633 /* vi: set ts=4 sw=4 expandtab: */ |