1 | /* |
2 | Copyright (C) 1997-2021 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 <stdio.h> |
16 | #include <stdlib.h> |
17 | #include <signal.h> |
18 | |
19 | #include "SDL.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 | |
27 | static SDL_sem *sem; |
28 | int alive; |
29 | |
30 | typedef struct Thread_State { |
31 | SDL_Thread * thread; |
32 | int number; |
33 | SDL_bool flag; |
34 | int loop_count; |
35 | int content_count; |
36 | } Thread_State; |
37 | |
38 | static void |
39 | killed(int sig) |
40 | { |
41 | alive = 0; |
42 | } |
43 | |
44 | static int SDLCALL |
45 | ThreadFuncRealWorld(void *data) |
46 | { |
47 | Thread_State *state = (Thread_State *) data; |
48 | while (alive) { |
49 | SDL_SemWait(sem); |
50 | SDL_Log("Thread number %d has got the semaphore (value = %d)!\n" , |
51 | state->number, SDL_SemValue(sem)); |
52 | SDL_Delay(200); |
53 | SDL_SemPost(sem); |
54 | SDL_Log("Thread number %d has released the semaphore (value = %d)!\n" , |
55 | state->number, SDL_SemValue(sem)); |
56 | ++state->loop_count; |
57 | SDL_Delay(1); /* For the scheduler */ |
58 | } |
59 | SDL_Log("Thread number %d exiting.\n" , state->number); |
60 | return 0; |
61 | } |
62 | |
63 | static void |
64 | TestRealWorld(int init_sem) { |
65 | Thread_State thread_states[NUM_THREADS] = { {0} }; |
66 | int i; |
67 | int loop_count; |
68 | |
69 | sem = SDL_CreateSemaphore(init_sem); |
70 | |
71 | SDL_Log("Running %d threads, semaphore value = %d\n" , NUM_THREADS, |
72 | init_sem); |
73 | alive = 1; |
74 | /* Create all the threads */ |
75 | for (i = 0; i < NUM_THREADS; ++i) { |
76 | char name[64]; |
77 | SDL_snprintf(name, sizeof (name), "Thread%u" , (unsigned int) i); |
78 | thread_states[i].number = i; |
79 | thread_states[i].thread = SDL_CreateThread(ThreadFuncRealWorld, name, (void *) &thread_states[i]); |
80 | } |
81 | |
82 | /* Wait 10 seconds */ |
83 | SDL_Delay(10 * 1000); |
84 | |
85 | /* Wait for all threads to finish */ |
86 | SDL_Log("Waiting for threads to finish\n" ); |
87 | alive = 0; |
88 | loop_count = 0; |
89 | for (i = 0; i < NUM_THREADS; ++i) { |
90 | SDL_WaitThread(thread_states[i].thread, NULL); |
91 | loop_count += thread_states[i].loop_count; |
92 | } |
93 | SDL_Log("Finished waiting for threads, ran %d loops in total\n\n" , loop_count); |
94 | |
95 | SDL_DestroySemaphore(sem); |
96 | } |
97 | |
98 | static void |
99 | TestWaitTimeout(void) |
100 | { |
101 | Uint32 start_ticks; |
102 | Uint32 end_ticks; |
103 | Uint32 duration; |
104 | int retval; |
105 | |
106 | sem = SDL_CreateSemaphore(0); |
107 | SDL_Log("Waiting 2 seconds on semaphore\n" ); |
108 | |
109 | start_ticks = SDL_GetTicks(); |
110 | retval = SDL_SemWaitTimeout(sem, 2000); |
111 | end_ticks = SDL_GetTicks(); |
112 | |
113 | duration = end_ticks - start_ticks; |
114 | |
115 | /* Accept a little offset in the effective wait */ |
116 | SDL_assert(duration > 1900 && duration < 2050); |
117 | SDL_Log("Wait took %d milliseconds\n\n" , duration); |
118 | |
119 | /* Check to make sure the return value indicates timed out */ |
120 | if (retval != SDL_MUTEX_TIMEDOUT) |
121 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_SemWaitTimeout returned: %d; expected: %d\n\n" , retval, SDL_MUTEX_TIMEDOUT); |
122 | |
123 | SDL_DestroySemaphore(sem); |
124 | } |
125 | |
126 | static void |
127 | TestOverheadUncontended(void) |
128 | { |
129 | Uint32 start_ticks; |
130 | Uint32 end_ticks; |
131 | Uint32 duration; |
132 | int i, j; |
133 | |
134 | sem = SDL_CreateSemaphore(0); |
135 | SDL_Log("Doing %d uncontended Post/Wait operations on semaphore\n" , NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT); |
136 | |
137 | start_ticks = SDL_GetTicks(); |
138 | for (i = 0; i < NUM_OVERHEAD_OPS_MULT; i++){ |
139 | for (j = 0; j < NUM_OVERHEAD_OPS; j++) { |
140 | SDL_SemPost(sem); |
141 | } |
142 | for (j = 0; j < NUM_OVERHEAD_OPS; j++) { |
143 | SDL_SemWait(sem); |
144 | } |
145 | } |
146 | end_ticks = SDL_GetTicks(); |
147 | |
148 | duration = end_ticks - start_ticks; |
149 | SDL_Log("Took %d milliseconds\n\n" , duration); |
150 | |
151 | SDL_DestroySemaphore(sem); |
152 | } |
153 | |
154 | static int SDLCALL |
155 | ThreadFuncOverheadContended(void *data) |
156 | { |
157 | Thread_State *state = (Thread_State *) data; |
158 | |
159 | if (state->flag) { |
160 | while(alive) { |
161 | if (SDL_SemTryWait(sem) == SDL_MUTEX_TIMEDOUT) { |
162 | ++state->content_count; |
163 | } |
164 | ++state->loop_count; |
165 | } |
166 | } else { |
167 | while(alive) { |
168 | /* Timeout needed to allow check on alive flag */ |
169 | if (SDL_SemWaitTimeout(sem, 50) == SDL_MUTEX_TIMEDOUT) { |
170 | ++state->content_count; |
171 | } |
172 | ++state->loop_count; |
173 | } |
174 | } |
175 | return 0; |
176 | } |
177 | |
178 | static void |
179 | TestOverheadContended(SDL_bool try_wait) |
180 | { |
181 | Uint32 start_ticks; |
182 | Uint32 end_ticks; |
183 | Uint32 duration; |
184 | Thread_State thread_states[NUM_THREADS] = { {0} }; |
185 | char textBuffer[1024]; |
186 | int loop_count; |
187 | int content_count; |
188 | int i, j; |
189 | size_t len; |
190 | |
191 | sem = SDL_CreateSemaphore(0); |
192 | SDL_Log("Doing %d contended %s operations on semaphore using %d threads\n" , |
193 | NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT, try_wait ? "Post/TryWait" : "Post/WaitTimeout" , NUM_THREADS); |
194 | alive = 1; |
195 | /* Create multiple threads to starve the semaphore and cause contention */ |
196 | for (i = 0; i < NUM_THREADS; ++i) { |
197 | char name[64]; |
198 | SDL_snprintf(name, sizeof (name), "Thread%u" , (unsigned int) i); |
199 | thread_states[i].flag = try_wait; |
200 | thread_states[i].thread = SDL_CreateThread(ThreadFuncOverheadContended, name, (void *) &thread_states[i]); |
201 | } |
202 | |
203 | start_ticks = SDL_GetTicks(); |
204 | for (i = 0; i < NUM_OVERHEAD_OPS_MULT; i++) { |
205 | for (j = 0; j < NUM_OVERHEAD_OPS; j++) { |
206 | SDL_SemPost(sem); |
207 | } |
208 | /* Make sure threads consumed everything */ |
209 | while (SDL_SemValue(sem)) { } |
210 | } |
211 | end_ticks = SDL_GetTicks(); |
212 | |
213 | alive = 0; |
214 | loop_count = 0; |
215 | content_count = 0; |
216 | for (i = 0; i < NUM_THREADS; ++i) { |
217 | SDL_WaitThread(thread_states[i].thread, NULL); |
218 | loop_count += thread_states[i].loop_count; |
219 | content_count += thread_states[i].content_count; |
220 | } |
221 | SDL_assert_release((loop_count - content_count) == NUM_OVERHEAD_OPS * NUM_OVERHEAD_OPS_MULT); |
222 | |
223 | duration = end_ticks - start_ticks; |
224 | SDL_Log("Took %d milliseconds, threads %s %d out of %d times in total (%.2f%%)\n" , |
225 | duration, try_wait ? "where contended" : "timed out" , content_count, |
226 | loop_count, ((float)content_count * 100) / loop_count); |
227 | /* Print how many semaphores where consumed per thread */ |
228 | SDL_snprintf(textBuffer, sizeof(textBuffer), "{ " ); |
229 | for (i = 0; i < NUM_THREADS; ++i) { |
230 | if (i > 0) { |
231 | len = SDL_strlen(textBuffer); |
232 | SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, ", " ); |
233 | } |
234 | len = SDL_strlen(textBuffer); |
235 | SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, "%d" , thread_states[i].loop_count - thread_states[i].content_count); |
236 | } |
237 | len = SDL_strlen(textBuffer); |
238 | SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, " }\n" ); |
239 | SDL_Log("%s\n" , textBuffer); |
240 | |
241 | SDL_DestroySemaphore(sem); |
242 | } |
243 | |
244 | int |
245 | main(int argc, char **argv) |
246 | { |
247 | int init_sem; |
248 | |
249 | /* Enable standard application logging */ |
250 | SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); |
251 | |
252 | if (argc < 2) { |
253 | SDL_Log("Usage: %s init_value\n" , argv[0]); |
254 | return (1); |
255 | } |
256 | |
257 | /* Load the SDL library */ |
258 | if (SDL_Init(0) < 0) { |
259 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n" , SDL_GetError()); |
260 | return (1); |
261 | } |
262 | signal(SIGTERM, killed); |
263 | signal(SIGINT, killed); |
264 | |
265 | init_sem = atoi(argv[1]); |
266 | if (init_sem > 0) { |
267 | TestRealWorld(init_sem); |
268 | } |
269 | |
270 | TestWaitTimeout(); |
271 | |
272 | TestOverheadUncontended(); |
273 | |
274 | TestOverheadContended(SDL_FALSE); |
275 | |
276 | TestOverheadContended(SDL_TRUE); |
277 | |
278 | SDL_Quit(); |
279 | return (0); |
280 | } |
281 | |