Horizon Official Technical Documentation
LockedLookupTableTest.cpp File Reference
#include "Core/Multithreading/LockedLookupTable.hpp"
#include <boost/test/unit_test.hpp>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <atomic>
#include <thread>
#include <array>
+ Include dependency graph for LockedLookupTableTest.cpp:

Macros

#define BOOST_TEST_MODULE   "LockedLookupTableTest"
 
#define MAX_THREADS   10
 
#define MAX_TABLE_SIZE   100000
 
#define STRING_SIZE   32
 
#define MAX_BUCKETS   MAX_TABLE_SIZE / 10
 
#define SPLIT_THREADS   true
 

Functions

 BOOST_AUTO_TEST_CASE (LockedLookupTableTest)
 This test checks read/writing between multiple threads simultaneously for a table of int and string keys. More...
 

Variables

std::atomic< bool > go {false}
 
std::atomic< bool > done_read {false}
 
std::atomic< bool > done_write {false}
 
std::atomic< bool > fail {false}
 

Macro Definition Documentation

◆ BOOST_TEST_MODULE

#define BOOST_TEST_MODULE   "LockedLookupTableTest"

◆ MAX_BUCKETS

#define MAX_BUCKETS   MAX_TABLE_SIZE / 10

◆ MAX_TABLE_SIZE

#define MAX_TABLE_SIZE   100000

◆ MAX_THREADS

#define MAX_THREADS   10

◆ SPLIT_THREADS

#define SPLIT_THREADS   true

◆ STRING_SIZE

#define STRING_SIZE   32

Function Documentation

◆ BOOST_AUTO_TEST_CASE()

BOOST_AUTO_TEST_CASE ( LockedLookupTableTest  )

This test checks read/writing between multiple threads simultaneously for a table of int and string keys.

It is normal for the string/string table to take longer than its int/int counterpart. The goal is to test for simlutaneous reading/writing only, not erasing. For erasing we would need to stop read/writing or pause before reloading. The table size is limited to 1000 only due to memory limits in docker containers for CI.

60{
61 clock_t int_begin = clock();
63 std::array<std::thread, MAX_THREADS> r;
64 std::array<std::thread, MAX_THREADS> w;
65
66#if SPLIT_THREADS
67 printf("Dividing work between %d reader + %d writer threads: %d per thread.\n", MAX_THREADS, MAX_THREADS, (MAX_TABLE_SIZE/MAX_THREADS));
68#endif
69
70 // Populate for reading
71 for (int j = 0; j < MAX_TABLE_SIZE; j++)
72 table.insert(j, j);
73
74 // Readers
75 for (int i = 0; i < MAX_THREADS; i++) {
76 r[i] = std::thread([&table, i] () {
77#if SPLIT_THREADS
78 int start = MAX_TABLE_SIZE / MAX_THREADS * i;
79 int end = start + MAX_TABLE_SIZE / MAX_THREADS;
80#else
81 int start = 0;
82 int end = MAX_TABLE_SIZE;
83#endif
84 while (!go);
85
86 clock_t t_begin = clock();
87
88 for (int j = start; j < end; j++) {
89 int val = table.at(j);
90 if (j != val) {
91 done_read.exchange(true);
92 fail.exchange(true);
93 return;
94 }
95 }
96
97 done_read.exchange(true);
98 printf("INT/INT Thread #%d - Read Time %.5fs\n", i, double(clock() - t_begin) / CLOCKS_PER_SEC);
99 });
100 }
101
102 // Writers
103 for (int i = 0; i < MAX_THREADS; i++) {
104 w[i] = std::thread([&table, i] () {
105#if SPLIT_THREADS
106 int start = MAX_TABLE_SIZE / MAX_THREADS * i;
107 int end = start + MAX_TABLE_SIZE / MAX_THREADS;
108#else
109 int start = 0;
110 int end = MAX_TABLE_SIZE;
111#endif
112 while (!go);
113
114 clock_t t_begin = clock();
115
116 for (int j = start; j < end; j++)
117 table.insert(j, j);
118
119 done_write.exchange(true);
120 printf("INT/INT Thread #%d - Write Time %.5fs\n", i, double(clock() - t_begin) / CLOCKS_PER_SEC);
121 });
122 }
123
124 go.exchange(true);
125
126 while (table.size() < MAX_TABLE_SIZE && !done_read && !done_write);
127
128 if (fail)
129 BOOST_FAIL("Failed read.");
130
131 printf("INT/INT Read/Write Test Finished in %0.5fs\n", double(clock() - int_begin) / CLOCKS_PER_SEC);
132
133 int_begin = clock();
134
135 // Map Iteration
136 std::map<int, int> map = table.get_map();
137 int i = 0;
138 for (auto c : map) {
139 BOOST_CHECK_EQUAL(i, c.second);
140 i++;
141 }
142
143 printf("INT/INT Map Iteration Test Finished in %0.5fs\n", double(clock() - int_begin) / CLOCKS_PER_SEC);
144
145 // Final Integrity Check
146 for (int j = 0; j < MAX_TABLE_SIZE; j++)
147 BOOST_CHECK_EQUAL(j, table.at(j));
148
149 printf("INT/INT Finished in %0.5fs\n", double(clock() - int_begin) / CLOCKS_PER_SEC);
150
151 for (int i = 0; i < MAX_THREADS; i++) {
152 if (r[i].joinable()) r[i].join();
153 if (w[i].joinable()) w[i].join();
154 }
155
156 done_read.exchange(false);
157 done_write.exchange(false);
158 go.exchange(false);
159
160 // Erase table contents.
161 for (int i = 0; i < MAX_THREADS; i++) {
162 w[i] = std::thread([&table, i] () {
163#if SPLIT_THREADS
164 int start = MAX_TABLE_SIZE / MAX_THREADS * i;
165 int end = start + MAX_TABLE_SIZE / MAX_THREADS;
166#else
167 int start = 0;
168 int end = MAX_TABLE_SIZE;
169#endif
170 while (!go);
171
172 clock_t t_begin = clock();
173
174 for (int j = start; j < end; j++)
175 table.erase(j);
176
177 printf("INT/INT Thread #%d - Erase Time %.5fs\n", i, double(clock() - t_begin) / CLOCKS_PER_SEC);
178 });
179 }
180
181 go.exchange(true);
182
183 while (table.size() > 0);
184
185 printf("INT/INT Collisions: %d\n", table.max_collisions());
186
187 for (int i = 0; i < MAX_THREADS; i++) {
188 if (w[i].joinable()) w[i].join();
189 }
190
191 done_read.exchange(false);
192 done_write.exchange(false);
193 go.exchange(false);
194
195 /*==================================*
196 * String Key / Value Test
197 *==================================*/
198 clock_t str_begin = clock();
199 std::string chars("abcdefghijklmnopqrstuvwxyz"
200 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
201 "1234567890"
202 "!@#$%^&*()"
203 "`~-_=+[{]}\\|;:'\",<.>/? ");
205 std::vector<std::string> keylist, vallist;
206
207 // Populate
208 for (int j = 0; j < MAX_TABLE_SIZE; j++) {
209 char key[STRING_SIZE + 1]{0}, val[STRING_SIZE + 1]{0};
210 for (int i = 0; i < STRING_SIZE; ++i) {
211 key[i] = chars[rand() % (chars.size() - 1)];
212 val[i] = chars[rand() % (chars.size() - 1)];
213 }
214
215 keylist.push_back(key);
216 vallist.push_back(val);
217 str_table.insert(key, val);
218 }
219
220 // Readers
221 for (int i = 0; i < MAX_THREADS; i++) {
222 r[i] = std::thread([&str_table, keylist, vallist, i] () {
223#if SPLIT_THREADS
224 int start = MAX_TABLE_SIZE / MAX_THREADS * i;
225 int end = start + MAX_TABLE_SIZE / MAX_THREADS;
226#else
227 int start = 0;
228 int end = MAX_TABLE_SIZE;
229#endif
230 while (!go);
231
232 clock_t t_begin = clock();
233
234 for (int j = start; j < end; j++) {
235 std::string val = str_table.at(keylist.at(j));
236 if (val.compare(vallist.at(j)) != 0) {
237 //printf("STR/STR not equal: %s != %s\n", vallist.at(j).c_str(), val.c_str());
238 done_read.exchange(true);
239 fail.exchange(true);
240 return;
241 }
242 }
243
244 done_read.exchange(true);
245 printf("STR/STR Thread #%d - Read Time %.5fs\n", i, double(clock() - t_begin) / CLOCKS_PER_SEC);
246 });
247 }
248
249 // Writers
250 for (int i = 0; i < MAX_THREADS; i++) {
251 w[i] = std::thread([&str_table, keylist, vallist, i] () {
252#if SPLIT_THREADS
253 int start = MAX_TABLE_SIZE / MAX_THREADS * i;
254 int end = start + MAX_TABLE_SIZE / MAX_THREADS;
255#else
256 int start = 0;
257 int end = MAX_TABLE_SIZE;
258#endif
259 while (!go);
260 clock_t t_begin = clock();
261
262 for (int j = start; j < end; j++)
263 str_table.insert(keylist.at(j), vallist.at(j));
264
265 done_write.exchange(true);
266 printf("STR/STR Thread #%d - Write Time %.5fs\n", i, double(clock() - t_begin) / CLOCKS_PER_SEC);
267 });
268 }
269
270 go.exchange(true);
271
272 while (!done_read && !done_write);
273
274 if (fail)
275 BOOST_FAIL("Failed read.");
276
277 for (int i = 0; i < MAX_THREADS; i++) {
278 if (r[i].joinable()) r[i].join();
279 if (w[i].joinable()) w[i].join();
280 }
281
282 printf("STR/STR Read/Write Test Finished in %0.6fs\n", double(clock() - str_begin) / CLOCKS_PER_SEC);
283
284 str_begin = clock();
285
286 // Map Iteration
287 std::map<std::string, std::string> strmap = str_table.get_map();
288 i = 0;
289 for (auto str : keylist) {
290 BOOST_CHECK_EQUAL(strmap.at(str).compare(vallist.at(i)), 0);
291 i++;
292 }
293
294 printf("STR/STR Map Iteration Test Finished in %0.6fs\n", double(clock() - str_begin) / CLOCKS_PER_SEC);
295
296 // Final Integrity Check
297 i = 0;
298 for (auto str : keylist) {
299 BOOST_CHECK_EQUAL(vallist.at(i).compare(str_table.at(str)), 0);
300 i++;
301 }
302
303 go.exchange(false);
304
305 // Erase table contents.
306 for (int i = 0; i < MAX_THREADS; i++) {
307 w[i] = std::thread([&str_table, keylist, i] () {
308#if SPLIT_THREADS
309 int start = MAX_TABLE_SIZE / MAX_THREADS * i;
310 int end = start + MAX_TABLE_SIZE / MAX_THREADS;
311#else
312 int start = 0;
313 int end = MAX_TABLE_SIZE;
314#endif
315 while (!go);
316
317 clock_t t_begin = clock();
318
319 for (int j = start; j < end; j++)
320 str_table.erase(keylist.at(j));
321
322 printf("STR/STR Thread #%d - Erase Time %.5fs\n", i, double(clock() - t_begin) / CLOCKS_PER_SEC);
323 });
324 }
325
326 go.exchange(true);
327
328 while (str_table.size() > 0);
329
330 printf("STR/STR Collisions: %d\n", str_table.max_collisions());
331
332 for (int i = 0; i < MAX_THREADS; i++) {
333 if (w[i].joinable()) w[i].join();
334 }
335}
#define MAX_TABLE_SIZE
Definition: LockedLookupTableTest.cpp:43
std::atomic< bool > done_write
Definition: LockedLookupTableTest.cpp:49
#define MAX_BUCKETS
Definition: LockedLookupTableTest.cpp:45
#define MAX_THREADS
Definition: LockedLookupTableTest.cpp:42
std::atomic< bool > fail
Definition: LockedLookupTableTest.cpp:49
std::atomic< bool > done_read
Definition: LockedLookupTableTest.cpp:49
std::atomic< bool > go
Definition: LockedLookupTableTest.cpp:49
#define STRING_SIZE
Definition: LockedLookupTableTest.cpp:44
Definition: LockedLookupTable.hpp:44

References LockedLookupTable< Key, Value, Hash >::at(), done_read, done_write, LockedLookupTable< Key, Value, Hash >::erase(), fail, LockedLookupTable< Key, Value, Hash >::get_map(), go, LockedLookupTable< Key, Value, Hash >::insert(), MAX_BUCKETS, LockedLookupTable< Key, Value, Hash >::max_collisions(), MAX_TABLE_SIZE, MAX_THREADS, LockedLookupTable< Key, Value, Hash >::size(), and STRING_SIZE.

+ Here is the call graph for this function:

Variable Documentation

◆ done_read

std::atomic<bool> done_read {false}

Referenced by BOOST_AUTO_TEST_CASE().

◆ done_write

std::atomic<bool> done_write {false}

Referenced by BOOST_AUTO_TEST_CASE().

◆ fail

std::atomic<bool> fail {false}

Referenced by BOOST_AUTO_TEST_CASE(), and GRF::load().

◆ go

std::atomic<bool> go {false}

Referenced by BOOST_AUTO_TEST_CASE().