Simple Directmedia Layer
at main 329 lines 9.4 kB view raw
1/* 2 Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org> 3 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. 7 8 Permission is granted to anyone to use this software for any purpose, 9 including commercial applications, and to alter it and redistribute it 10 freely. 11*/ 12 13/* Simple test of the SDL semaphore code */ 14 15#include <signal.h> 16 17#include <SDL3/SDL.h> 18#include <SDL3/SDL_main.h> 19#include <SDL3/SDL_test.h> 20 21#define NUM_THREADS 10 22/* This value should be smaller than the maximum count of the */ 23/* semaphore implementation: */ 24#define NUM_OVERHEAD_OPS 10000 25#define NUM_OVERHEAD_OPS_MULT 10 26 27static SDL_Semaphore *sem; 28static int alive; 29 30typedef struct Thread_State 31{ 32 SDL_Thread *thread; 33 int number; 34 bool flag; 35 int loop_count; 36 int content_count; 37} Thread_State; 38 39static void log_usage(char *progname, SDLTest_CommonState *state) { 40 static const char *options[] = { "[--no-threads]", "init_value", NULL }; 41 SDLTest_CommonLogUsage(state, progname, options); 42} 43 44static void 45killed(int sig) 46{ 47 alive = 0; 48} 49 50static int SDLCALL 51ThreadFuncRealWorld(void *data) 52{ 53 Thread_State *state = (Thread_State *)data; 54 while (alive) { 55 SDL_WaitSemaphore(sem); 56 SDL_Log("Thread number %d has got the semaphore (value = %" SDL_PRIu32 ")!\n", 57 state->number, SDL_GetSemaphoreValue(sem)); 58 SDL_Delay(200); 59 SDL_SignalSemaphore(sem); 60 SDL_Log("Thread number %d has released the semaphore (value = %" SDL_PRIu32 ")!\n", 61 state->number, SDL_GetSemaphoreValue(sem)); 62 ++state->loop_count; 63 SDL_Delay(1); /* For the scheduler */ 64 } 65 SDL_Log("Thread number %d exiting.\n", state->number); 66 return 0; 67} 68 69static void 70TestRealWorld(int init_sem) 71{ 72 Thread_State thread_states[NUM_THREADS] = { { 0 } }; 73 int i; 74 int loop_count; 75 76 sem = SDL_CreateSemaphore(init_sem); 77 78 SDL_Log("Running %d threads, semaphore value = %d\n", NUM_THREADS, 79 init_sem); 80 alive = 1; 81 /* Create all the threads */ 82 for (i = 0; i < NUM_THREADS; ++i) { 83 char name[64]; 84 (void)SDL_snprintf(name, sizeof(name), "Thread%u", (unsigned int)i); 85 thread_states[i].number = i; 86 thread_states[i].thread = SDL_CreateThread(ThreadFuncRealWorld, name, (void *)&thread_states[i]); 87 } 88 89 /* Wait 10 seconds */ 90 SDL_Delay(10 * 1000); 91 92 /* Wait for all threads to finish */ 93 SDL_Log("Waiting for threads to finish\n"); 94 alive = 0; 95 loop_count = 0; 96 for (i = 0; i < NUM_THREADS; ++i) { 97 SDL_WaitThread(thread_states[i].thread, NULL); 98 loop_count += thread_states[i].loop_count; 99 } 100 SDL_Log("Finished waiting for threads, ran %d loops in total\n\n", loop_count); 101 102 SDL_DestroySemaphore(sem); 103} 104 105static void 106TestWaitTimeout(void) 107{ 108 Uint64 start_ticks; 109 Uint64 end_ticks; 110 Uint64 duration; 111 bool result; 112 113 sem = SDL_CreateSemaphore(0); 114 SDL_Log("Waiting 2 seconds on semaphore\n"); 115 116 start_ticks = SDL_GetTicks(); 117 result = SDL_WaitSemaphoreTimeout(sem, 2000); 118 end_ticks = SDL_GetTicks(); 119 120 duration = end_ticks - start_ticks; 121 122 /* Accept a little offset in the effective wait */ 123 SDL_Log("Wait took %" SDL_PRIu64 " milliseconds\n\n", duration); 124 SDL_assert(duration > 1900 && duration < 2050); 125 126 /* Check to make sure the return value indicates timed out */ 127 if (result) { 128 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_WaitSemaphoreTimeout returned: %d; expected: false\n\n", result); 129 } 130 131 SDL_DestroySemaphore(sem); 132} 133 134static void 135TestOverheadUncontended(void) 136{ 137 Uint64 start_ticks; 138 Uint64 end_ticks; 139 Uint64 duration; 140 int i, j; 141 142 sem = SDL_CreateSemaphore(0); 143 SDL_Log("Doing %d uncontended Post/Wait operations on semaphore\n", NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT); 144 145 start_ticks = SDL_GetTicks(); 146 for (i = 0; i < NUM_OVERHEAD_OPS_MULT; i++) { 147 for (j = 0; j < NUM_OVERHEAD_OPS; j++) { 148 SDL_SignalSemaphore(sem); 149 } 150 for (j = 0; j < NUM_OVERHEAD_OPS; j++) { 151 SDL_WaitSemaphore(sem); 152 } 153 } 154 end_ticks = SDL_GetTicks(); 155 156 duration = end_ticks - start_ticks; 157 SDL_Log("Took %" SDL_PRIu64 " milliseconds\n\n", duration); 158 159 SDL_DestroySemaphore(sem); 160} 161 162static int SDLCALL 163ThreadFuncOverheadContended(void *data) 164{ 165 Thread_State *state = (Thread_State *)data; 166 167 if (state->flag) { 168 while (alive) { 169 if (!SDL_TryWaitSemaphore(sem)) { 170 ++state->content_count; 171 } 172 ++state->loop_count; 173 } 174 } else { 175 while (alive) { 176 /* Timeout needed to allow check on alive flag */ 177 if (!SDL_WaitSemaphoreTimeout(sem, 50)) { 178 ++state->content_count; 179 } 180 ++state->loop_count; 181 } 182 } 183 return 0; 184} 185 186static void 187TestOverheadContended(bool try_wait) 188{ 189 Uint64 start_ticks; 190 Uint64 end_ticks; 191 Uint64 duration; 192 Thread_State thread_states[NUM_THREADS] = { { 0 } }; 193 char textBuffer[1024]; 194 int loop_count; 195 int content_count; 196 int i, j; 197 size_t len; 198 199 sem = SDL_CreateSemaphore(0); 200 SDL_Log("Doing %d contended %s operations on semaphore using %d threads\n", 201 NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT, try_wait ? "Post/TryWait" : "Post/WaitTimeout", NUM_THREADS); 202 alive = 1; 203 /* Create multiple threads to starve the semaphore and cause contention */ 204 for (i = 0; i < NUM_THREADS; ++i) { 205 char name[64]; 206 (void)SDL_snprintf(name, sizeof(name), "Thread%u", (unsigned int)i); 207 thread_states[i].flag = try_wait; 208 thread_states[i].thread = SDL_CreateThread(ThreadFuncOverheadContended, name, (void *)&thread_states[i]); 209 } 210 211 start_ticks = SDL_GetTicks(); 212 for (i = 0; i < NUM_OVERHEAD_OPS_MULT; i++) { 213 for (j = 0; j < NUM_OVERHEAD_OPS; j++) { 214 SDL_SignalSemaphore(sem); 215 } 216 /* Make sure threads consumed everything */ 217 while (SDL_GetSemaphoreValue(sem)) { 218 /* Friendlier with cooperative threading models */ 219 SDL_DelayNS(1); 220 } 221 } 222 end_ticks = SDL_GetTicks(); 223 224 alive = 0; 225 loop_count = 0; 226 content_count = 0; 227 for (i = 0; i < NUM_THREADS; ++i) { 228 SDL_WaitThread(thread_states[i].thread, NULL); 229 loop_count += thread_states[i].loop_count; 230 content_count += thread_states[i].content_count; 231 } 232 SDL_assert_release((loop_count - content_count) == NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT); 233 234 duration = end_ticks - start_ticks; 235 SDL_Log("Took %" SDL_PRIu64 " milliseconds, threads %s %d out of %d times in total (%.2f%%)\n", 236 duration, try_wait ? "where contended" : "timed out", content_count, 237 loop_count, ((float)content_count * 100) / loop_count); 238 /* Print how many semaphores where consumed per thread */ 239 (void)SDL_snprintf(textBuffer, sizeof(textBuffer), "{ "); 240 for (i = 0; i < NUM_THREADS; ++i) { 241 if (i > 0) { 242 len = SDL_strlen(textBuffer); 243 (void)SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, ", "); 244 } 245 len = SDL_strlen(textBuffer); 246 (void)SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, "%d", thread_states[i].loop_count - thread_states[i].content_count); 247 } 248 len = SDL_strlen(textBuffer); 249 (void)SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, " }\n"); 250 SDL_Log("%s\n", textBuffer); 251 252 SDL_DestroySemaphore(sem); 253} 254 255int main(int argc, char **argv) 256{ 257 int arg_count = 0; 258 int i; 259 int init_sem = 0; 260 bool enable_threads = true; 261 SDLTest_CommonState *state; 262 263 /* Initialize test framework */ 264 state = SDLTest_CommonCreateState(argv, 0); 265 if (!state) { 266 return 1; 267 } 268 269 /* Parse commandline */ 270 for (i = 1; i < argc;) { 271 int consumed; 272 273 consumed = SDLTest_CommonArg(state, i); 274 if (consumed == 0) { 275 consumed = -1; 276 if (SDL_strcasecmp(argv[i], "--no-threads") == 0) { 277 enable_threads = false; 278 consumed = 1; 279 } else if (arg_count == 0) { 280 char *endptr; 281 init_sem = SDL_strtol(argv[i], &endptr, 0); 282 if (endptr != argv[i] && *endptr == '\0') { 283 arg_count++; 284 consumed = 1; 285 } 286 } 287 } 288 if (consumed <= 0) { 289 log_usage(argv[0], state); 290 return 1; 291 } 292 293 i += consumed; 294 } 295 296 if (arg_count != 1) { 297 log_usage(argv[0], state); 298 return 1; 299 } 300 301 /* Load the SDL library */ 302 if (!SDL_Init(0)) { 303 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError()); 304 return 1; 305 } 306 (void)signal(SIGTERM, killed); 307 (void)signal(SIGINT, killed); 308 309 if (enable_threads) { 310 if (init_sem > 0) { 311 TestRealWorld(init_sem); 312 } 313 314 TestWaitTimeout(); 315 } 316 317 TestOverheadUncontended(); 318 319 if (enable_threads) { 320 TestOverheadContended(false); 321 322 TestOverheadContended(true); 323 } 324 325 SDL_Quit(); 326 SDLTest_CommonDestroyState(state); 327 328 return 0; 329}