2 Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
17 Absolutely basic tests just to see if we get the expected value
18 after calling each function.
25 static char *t = "TRUE";
26 static char *f = "FALSE";
40 SDL_SpinLock lock = 0;
43 SDL_bool tfret = SDL_FALSE;
45 SDL_Log("\nspin lock---------------------------------------\n\n");
47 SDL_AtomicLock(&lock);
48 SDL_Log("AtomicLock lock=%d\n", lock);
49 SDL_AtomicUnlock(&lock);
50 SDL_Log("AtomicUnlock lock=%d\n", lock);
52 SDL_Log("\natomic -----------------------------------------\n\n");
55 tfret = SDL_AtomicSet(&v, 10) == 0 ? SDL_TRUE : SDL_FALSE;
56 SDL_Log("AtomicSet(10) tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
57 tfret = SDL_AtomicAdd(&v, 10) == 10 ? SDL_TRUE : SDL_FALSE;
58 SDL_Log("AtomicAdd(10) tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
62 tfret = (SDL_AtomicGet(&v) == 1) ? SDL_TRUE : SDL_FALSE;
63 SDL_Log("AtomicIncRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
65 tfret = (SDL_AtomicGet(&v) == 2) ? SDL_TRUE : SDL_FALSE;
66 SDL_Log("AtomicIncRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
67 tfret = (SDL_AtomicDecRef(&v) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE;
68 SDL_Log("AtomicDecRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
69 tfret = (SDL_AtomicDecRef(&v) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE;
70 SDL_Log("AtomicDecRef() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
72 SDL_AtomicSet(&v, 10);
73 tfret = (SDL_AtomicCAS(&v, 0, 20) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE;
74 SDL_Log("AtomicCAS() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
75 value = SDL_AtomicGet(&v);
76 tfret = (SDL_AtomicCAS(&v, value, 20) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE;
77 SDL_Log("AtomicCAS() tfret=%s val=%d\n", tf(tfret), SDL_AtomicGet(&v));
80 /**************************************************************************/
81 /* Atomic operation test
82 * Adapted with permission from code by Michael Davidsaver at:
83 * http://bazaar.launchpad.net/~mdavidsaver/epics-base/atomic/revision/12105#src/libCom/test/epicsAtomicTest.c
84 * Original copyright 2010 Brookhaven Science Associates as operator of Brookhaven National Lab
85 * http://www.aps.anl.gov/epics/license/open.php
88 /* Tests semantics of atomic operations. Also a stress test
89 * to see if they are really atomic.
91 * Several threads adding to the same variable.
92 * at the end the value is compared with the expected
93 * and with a non-atomic counter.
96 /* Number of concurrent incrementers */
99 #define VALBITS (sizeof(atomicValue)*8)
101 #define atomicValue int
102 #define CountTo ((atomicValue)((unsigned int)(1<<(VALBITS-1))-1))
103 #define NInter (CountTo/CountInc/NThreads)
104 #define Expect (CountTo-NInter*CountInc*NThreads)
107 CountTo_GreaterThanZero = CountTo > 0,
109 SDL_COMPILE_TIME_ASSERT(size, CountTo_GreaterThanZero); /* check for rollover */
111 static SDL_atomic_t good = { 42 };
113 static atomicValue bad = 42;
115 static SDL_atomic_t threadsRunning;
117 static SDL_sem *threadDone;
120 int SDLCALL adder(void* junk)
122 unsigned long N=NInter;
123 SDL_Log("Thread subtracting %d %lu times\n",CountInc,N);
125 SDL_AtomicAdd(&good, -CountInc);
128 SDL_AtomicAdd(&threadsRunning, -1);
129 SDL_SemPost(threadDone);
139 start = SDL_GetTicks();
141 threadDone = SDL_CreateSemaphore(0);
143 SDL_AtomicSet(&threadsRunning, NThreads);
146 SDL_CreateThread(adder, "Adder", NULL);
148 while (SDL_AtomicGet(&threadsRunning) > 0)
149 SDL_SemWait(threadDone);
151 SDL_DestroySemaphore(threadDone);
153 end = SDL_GetTicks();
155 SDL_Log("Finished in %f sec\n", (end - start) / 1000.f);
164 SDL_Log("\nepic test---------------------------------------\n\n");
166 SDL_Log("Size asserted to be >= 32-bit\n");
167 SDL_assert(sizeof(atomicValue)>=4);
169 SDL_Log("Check static initializer\n");
170 v=SDL_AtomicGet(&good);
175 SDL_Log("Test negative values\n");
176 SDL_AtomicSet(&good, -5);
177 v=SDL_AtomicGet(&good);
180 SDL_Log("Verify maximum value\n");
181 SDL_AtomicSet(&good, CountTo);
182 v=SDL_AtomicGet(&good);
183 SDL_assert(v==CountTo);
185 SDL_Log("Test compare and exchange\n");
187 b=SDL_AtomicCAS(&good, 500, 43);
188 SDL_assert(!b); /* no swap since CountTo!=500 */
189 v=SDL_AtomicGet(&good);
190 SDL_assert(v==CountTo); /* ensure no swap */
192 b=SDL_AtomicCAS(&good, CountTo, 44);
193 SDL_assert(!!b); /* will swap */
194 v=SDL_AtomicGet(&good);
197 SDL_Log("Test Add\n");
199 v=SDL_AtomicAdd(&good, 1);
201 v=SDL_AtomicGet(&good);
204 v=SDL_AtomicAdd(&good, 10);
206 v=SDL_AtomicGet(&good);
209 SDL_Log("Test Add (Negative values)\n");
211 v=SDL_AtomicAdd(&good, -20);
213 v=SDL_AtomicGet(&good);
216 v=SDL_AtomicAdd(&good, -50); /* crossing zero down */
218 v=SDL_AtomicGet(&good);
221 v=SDL_AtomicAdd(&good, 30); /* crossing zero up */
223 v=SDL_AtomicGet(&good);
226 SDL_Log("Reset before count down test\n");
227 SDL_AtomicSet(&good, CountTo);
228 v=SDL_AtomicGet(&good);
229 SDL_assert(v==CountTo);
232 SDL_assert(bad==CountTo);
234 SDL_Log("Counting down from %d, Expect %d remaining\n",CountTo,Expect);
237 v=SDL_AtomicGet(&good);
238 SDL_Log("Atomic %d Non-Atomic %d\n",v,bad);
239 SDL_assert(v==Expect);
240 SDL_assert(bad!=Expect);
243 /* End atomic operation test */
244 /**************************************************************************/
246 /**************************************************************************/
247 /* Lock-free FIFO test */
249 /* This is useful to test the impact of another thread locking the queue
250 entirely for heavy-weight manipulation.
252 #define TEST_SPINLOCK_FIFO
254 #define NUM_READERS 4
255 #define NUM_WRITERS 4
256 #define EVENTS_PER_WRITER 1000000
258 /* The number of entries must be a power of 2 */
259 #define MAX_ENTRIES 256
260 #define WRAP_MASK (MAX_ENTRIES-1)
264 SDL_atomic_t sequence;
266 } SDL_EventQueueEntry;
270 SDL_EventQueueEntry entries[MAX_ENTRIES];
272 char cache_pad1[SDL_CACHELINE_SIZE-((sizeof(SDL_EventQueueEntry)*MAX_ENTRIES)%SDL_CACHELINE_SIZE)];
274 SDL_atomic_t enqueue_pos;
276 char cache_pad2[SDL_CACHELINE_SIZE-sizeof(SDL_atomic_t)];
278 SDL_atomic_t dequeue_pos;
280 char cache_pad3[SDL_CACHELINE_SIZE-sizeof(SDL_atomic_t)];
282 #ifdef TEST_SPINLOCK_FIFO
284 SDL_atomic_t rwcount;
285 SDL_atomic_t watcher;
287 char cache_pad4[SDL_CACHELINE_SIZE-sizeof(SDL_SpinLock)-2*sizeof(SDL_atomic_t)];
292 /* Only needed for the mutex test */
297 static void InitEventQueue(SDL_EventQueue *queue)
301 for (i = 0; i < MAX_ENTRIES; ++i) {
302 SDL_AtomicSet(&queue->entries[i].sequence, i);
304 SDL_AtomicSet(&queue->enqueue_pos, 0);
305 SDL_AtomicSet(&queue->dequeue_pos, 0);
306 #ifdef TEST_SPINLOCK_FIFO
308 SDL_AtomicSet(&queue->rwcount, 0);
309 SDL_AtomicSet(&queue->watcher, 0);
311 SDL_AtomicSet(&queue->active, 1);
314 static SDL_bool EnqueueEvent_LockFree(SDL_EventQueue *queue, const SDL_Event *event)
316 SDL_EventQueueEntry *entry;
322 #ifdef TEST_SPINLOCK_FIFO
323 /* This is a gate so an external thread can lock the queue */
324 SDL_AtomicLock(&queue->lock);
325 SDL_assert(SDL_AtomicGet(&queue->watcher) == 0);
326 SDL_AtomicIncRef(&queue->rwcount);
327 SDL_AtomicUnlock(&queue->lock);
330 queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos);
332 entry = &queue->entries[queue_pos & WRAP_MASK];
333 entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence);
335 delta = (int)(entry_seq - queue_pos);
337 /* The entry and the queue position match, try to increment the queue position */
338 if (SDL_AtomicCAS(&queue->enqueue_pos, (int)queue_pos, (int)(queue_pos+1))) {
339 /* We own the object, fill it! */
340 entry->event = *event;
341 SDL_AtomicSet(&entry->sequence, (int)(queue_pos + 1));
345 } else if (delta < 0) {
346 /* We ran into an old queue entry, which means it still needs to be dequeued */
350 /* We ran into a new queue entry, get the new queue position */
351 queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos);
355 #ifdef TEST_SPINLOCK_FIFO
356 SDL_AtomicDecRef(&queue->rwcount);
361 static SDL_bool DequeueEvent_LockFree(SDL_EventQueue *queue, SDL_Event *event)
363 SDL_EventQueueEntry *entry;
369 #ifdef TEST_SPINLOCK_FIFO
370 /* This is a gate so an external thread can lock the queue */
371 SDL_AtomicLock(&queue->lock);
372 SDL_assert(SDL_AtomicGet(&queue->watcher) == 0);
373 SDL_AtomicIncRef(&queue->rwcount);
374 SDL_AtomicUnlock(&queue->lock);
377 queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos);
379 entry = &queue->entries[queue_pos & WRAP_MASK];
380 entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence);
382 delta = (int)(entry_seq - (queue_pos + 1));
384 /* The entry and the queue position match, try to increment the queue position */
385 if (SDL_AtomicCAS(&queue->dequeue_pos, (int)queue_pos, (int)(queue_pos+1))) {
386 /* We own the object, fill it! */
387 *event = entry->event;
388 SDL_AtomicSet(&entry->sequence, (int)(queue_pos+MAX_ENTRIES));
392 } else if (delta < 0) {
393 /* We ran into an old queue entry, which means we've hit empty */
397 /* We ran into a new queue entry, get the new queue position */
398 queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos);
402 #ifdef TEST_SPINLOCK_FIFO
403 SDL_AtomicDecRef(&queue->rwcount);
408 static SDL_bool EnqueueEvent_Mutex(SDL_EventQueue *queue, const SDL_Event *event)
410 SDL_EventQueueEntry *entry;
414 SDL_bool status = SDL_FALSE;
416 SDL_LockMutex(queue->mutex);
418 queue_pos = (unsigned)queue->enqueue_pos.value;
419 entry = &queue->entries[queue_pos & WRAP_MASK];
420 entry_seq = (unsigned)entry->sequence.value;
422 delta = (int)(entry_seq - queue_pos);
424 ++queue->enqueue_pos.value;
426 /* We own the object, fill it! */
427 entry->event = *event;
428 entry->sequence.value = (int)(queue_pos + 1);
430 } else if (delta < 0) {
431 /* We ran into an old queue entry, which means it still needs to be dequeued */
433 SDL_Log("ERROR: mutex failed!\n");
436 SDL_UnlockMutex(queue->mutex);
441 static SDL_bool DequeueEvent_Mutex(SDL_EventQueue *queue, SDL_Event *event)
443 SDL_EventQueueEntry *entry;
447 SDL_bool status = SDL_FALSE;
449 SDL_LockMutex(queue->mutex);
451 queue_pos = (unsigned)queue->dequeue_pos.value;
452 entry = &queue->entries[queue_pos & WRAP_MASK];
453 entry_seq = (unsigned)entry->sequence.value;
455 delta = (int)(entry_seq - (queue_pos + 1));
457 ++queue->dequeue_pos.value;
459 /* We own the object, fill it! */
460 *event = entry->event;
461 entry->sequence.value = (int)(queue_pos + MAX_ENTRIES);
463 } else if (delta < 0) {
464 /* We ran into an old queue entry, which means we've hit empty */
466 SDL_Log("ERROR: mutex failed!\n");
469 SDL_UnlockMutex(queue->mutex);
474 static SDL_sem *writersDone;
475 static SDL_sem *readersDone;
476 static SDL_atomic_t writersRunning;
477 static SDL_atomic_t readersRunning;
481 SDL_EventQueue *queue;
483 char padding1[SDL_CACHELINE_SIZE-(sizeof(SDL_EventQueue*)+sizeof(int))%SDL_CACHELINE_SIZE];
486 char padding2[SDL_CACHELINE_SIZE-sizeof(int)-sizeof(SDL_bool)];
491 SDL_EventQueue *queue;
492 int counters[NUM_WRITERS];
495 char padding[SDL_CACHELINE_SIZE-(sizeof(SDL_EventQueue*)+sizeof(int)*NUM_WRITERS+sizeof(int)+sizeof(SDL_bool))%SDL_CACHELINE_SIZE];
498 static int SDLCALL FIFO_Writer(void* _data)
500 WriterData *data = (WriterData *)_data;
501 SDL_EventQueue *queue = data->queue;
505 event.type = SDL_USEREVENT;
506 event.user.windowID = 0;
508 event.user.data1 = data;
509 event.user.data2 = NULL;
511 if (data->lock_free) {
512 for (i = 0; i < EVENTS_PER_WRITER; ++i) {
514 while (!EnqueueEvent_LockFree(queue, &event)) {
520 for (i = 0; i < EVENTS_PER_WRITER; ++i) {
522 while (!EnqueueEvent_Mutex(queue, &event)) {
528 SDL_AtomicAdd(&writersRunning, -1);
529 SDL_SemPost(writersDone);
533 static int SDLCALL FIFO_Reader(void* _data)
535 ReaderData *data = (ReaderData *)_data;
536 SDL_EventQueue *queue = data->queue;
539 if (data->lock_free) {
541 if (DequeueEvent_LockFree(queue, &event)) {
542 WriterData *writer = (WriterData*)event.user.data1;
543 ++data->counters[writer->index];
544 } else if (SDL_AtomicGet(&queue->active)) {
548 /* We drained the queue, we're done! */
554 if (DequeueEvent_Mutex(queue, &event)) {
555 WriterData *writer = (WriterData*)event.user.data1;
556 ++data->counters[writer->index];
557 } else if (SDL_AtomicGet(&queue->active)) {
561 /* We drained the queue, we're done! */
566 SDL_AtomicAdd(&readersRunning, -1);
567 SDL_SemPost(readersDone);
571 #ifdef TEST_SPINLOCK_FIFO
572 /* This thread periodically locks the queue for no particular reason */
573 static int SDLCALL FIFO_Watcher(void* _data)
575 SDL_EventQueue *queue = (SDL_EventQueue *)_data;
577 while (SDL_AtomicGet(&queue->active)) {
578 SDL_AtomicLock(&queue->lock);
579 SDL_AtomicIncRef(&queue->watcher);
580 while (SDL_AtomicGet(&queue->rwcount) > 0) {
583 /* Do queue manipulation here... */
584 SDL_AtomicDecRef(&queue->watcher);
585 SDL_AtomicUnlock(&queue->lock);
592 #endif /* TEST_SPINLOCK_FIFO */
594 static void RunFIFOTest(SDL_bool lock_free)
596 SDL_EventQueue queue;
597 WriterData writerData[NUM_WRITERS];
598 ReaderData readerData[NUM_READERS];
602 char textBuffer[1024];
605 SDL_Log("\nFIFO test---------------------------------------\n\n");
606 SDL_Log("Mode: %s\n", lock_free ? "LockFree" : "Mutex");
608 readersDone = SDL_CreateSemaphore(0);
609 writersDone = SDL_CreateSemaphore(0);
611 SDL_memset(&queue, 0xff, sizeof(queue));
613 InitEventQueue(&queue);
615 queue.mutex = SDL_CreateMutex();
618 start = SDL_GetTicks();
620 #ifdef TEST_SPINLOCK_FIFO
621 /* Start a monitoring thread */
623 SDL_CreateThread(FIFO_Watcher, "FIFOWatcher", &queue);
627 /* Start the readers first */
628 SDL_Log("Starting %d readers\n", NUM_READERS);
629 SDL_zero(readerData);
630 SDL_AtomicSet(&readersRunning, NUM_READERS);
631 for (i = 0; i < NUM_READERS; ++i) {
633 SDL_snprintf(name, sizeof (name), "FIFOReader%d", i);
634 readerData[i].queue = &queue;
635 readerData[i].lock_free = lock_free;
636 SDL_CreateThread(FIFO_Reader, name, &readerData[i]);
639 /* Start up the writers */
640 SDL_Log("Starting %d writers\n", NUM_WRITERS);
641 SDL_zero(writerData);
642 SDL_AtomicSet(&writersRunning, NUM_WRITERS);
643 for (i = 0; i < NUM_WRITERS; ++i) {
645 SDL_snprintf(name, sizeof (name), "FIFOWriter%d", i);
646 writerData[i].queue = &queue;
647 writerData[i].index = i;
648 writerData[i].lock_free = lock_free;
649 SDL_CreateThread(FIFO_Writer, name, &writerData[i]);
652 /* Wait for the writers */
653 while (SDL_AtomicGet(&writersRunning) > 0) {
654 SDL_SemWait(writersDone);
657 /* Shut down the queue so readers exit */
658 SDL_AtomicSet(&queue.active, 0);
660 /* Wait for the readers */
661 while (SDL_AtomicGet(&readersRunning) > 0) {
662 SDL_SemWait(readersDone);
665 end = SDL_GetTicks();
667 SDL_DestroySemaphore(readersDone);
668 SDL_DestroySemaphore(writersDone);
671 SDL_DestroyMutex(queue.mutex);
674 SDL_Log("Finished in %f sec\n", (end - start) / 1000.f);
677 for (i = 0; i < NUM_WRITERS; ++i) {
678 SDL_Log("Writer %d wrote %d events, had %d waits\n", i, EVENTS_PER_WRITER, writerData[i].waits);
680 SDL_Log("Writers wrote %d total events\n", NUM_WRITERS*EVENTS_PER_WRITER);
682 /* Print a breakdown of which readers read messages from which writer */
685 for (i = 0; i < NUM_READERS; ++i) {
687 for (j = 0; j < NUM_WRITERS; ++j) {
688 total += readerData[i].counters[j];
690 grand_total += total;
691 SDL_Log("Reader %d read %d events, had %d waits\n", i, total, readerData[i].waits);
692 SDL_snprintf(textBuffer, sizeof(textBuffer), " { ");
693 for (j = 0; j < NUM_WRITERS; ++j) {
695 len = SDL_strlen(textBuffer);
696 SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, ", ");
698 len = SDL_strlen(textBuffer);
699 SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, "%d", readerData[i].counters[j]);
701 len = SDL_strlen(textBuffer);
702 SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, " }\n");
703 SDL_Log("%s", textBuffer);
705 SDL_Log("Readers read %d total events\n", grand_total);
709 /**************************************************************************/
712 main(int argc, char *argv[])
714 /* Enable standard application logging */
715 SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
719 /* This test is really slow, so don't run it by default */
721 RunFIFOTest(SDL_FALSE);
723 RunFIFOTest(SDL_TRUE);
727 /* vi: set ts=4 sw=4 expandtab: */