1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4
5
6 #include <algorithm>
7 #include <functional>
8 #include <iostream>
9
10 #include <OpenImageIO/argparse.h>
11 #include <OpenImageIO/benchmark.h>
12 #include <OpenImageIO/sysutil.h>
13 #include <OpenImageIO/thread.h>
14 #include <OpenImageIO/timer.h>
15 #include <OpenImageIO/unittest.h>
16 #include <OpenImageIO/ustring.h>
17
18 using namespace OIIO;
19
20 static int iterations = 100000;
21 static int numthreads = 16;
22 static int ntrials = 1;
23 static bool verbose = false;
24 static bool wedge = false;
25 static int threadcounts[] = { 1, 2, 4, 8, 12, 16, 20,
26 24, 28, 32, 64, 128, 1024, 1 << 30 };
27
28
29
30 static void
getargs(int argc,char * argv[])31 getargs(int argc, char* argv[])
32 {
33 // clang-format off
34 ArgParse ap;
35 ap.intro("thread_test\n" OIIO_INTRO_STRING)
36 .usage("thread_test [options]");
37
38 ap.arg("-v", &verbose)
39 .help("Verbose mode");
40 ap.arg("--threads %d", &numthreads)
41 .help(Strutil::sprintf("Number of threads (default: %d)", numthreads));
42 ap.arg("--iters %d", &iterations)
43 .help(Strutil::sprintf("Number of iterations (default: %d)", iterations));
44 ap.arg("--trials %d", &ntrials)
45 .help("Number of trials");
46 ap.arg("--wedge", &wedge)
47 .help("Do a wedge test");
48 // clang-format on
49
50 ap.parse(argc, (const char**)argv);
51 }
52
53
54
55 void
do_nothing(int)56 do_nothing(int /*thread_id*/)
57 {
58 }
59
60
61
62 void
time_thread_group()63 time_thread_group()
64 {
65 std::cout << "\nTiming how long it takes to start/end thread_group:\n";
66 std::cout << "threads\ttime (best of " << ntrials << ")\n";
67 std::cout << "-------\t----------\n";
68 for (int i = 0; threadcounts[i] <= numthreads; ++i) {
69 int nt = wedge ? threadcounts[i] : numthreads;
70 int its = iterations / nt;
71
72 // make a lambda function that spawns a bunch of threads, calls a
73 // trivial function, then waits for them to finish and tears down
74 // the group.
75 auto func = [=]() {
76 thread_group g;
77 for (int i = 0; i < nt; ++i)
78 g.create_thread(do_nothing, i);
79 g.join_all();
80 };
81
82 double range;
83 double t = time_trial(func, ntrials, its, &range);
84
85 Strutil::printf("%2d\t%5.1f launch %8.1f threads/sec\n", nt, t,
86 (nt * its) / t);
87 if (!wedge)
88 break; // don't loop if we're not wedging
89 }
90 }
91
92
93
94 void
time_thread_pool()95 time_thread_pool()
96 {
97 std::cout << "\nTiming how long it takes to launch from thread_pool:\n";
98 std::cout << "threads\ttime (best of " << ntrials << ")\n";
99 std::cout << "-------\t----------\n";
100 thread_pool* pool(default_thread_pool());
101 for (int i = 0; threadcounts[i] <= numthreads; ++i) {
102 int nt = wedge ? threadcounts[i] : numthreads;
103 pool->resize(nt);
104 int its = iterations / nt;
105
106 // make a lambda function that spawns a bunch of threads, calls a
107 // trivial function, then waits for them to finish and tears down
108 // the group.
109 auto func = [=]() {
110 task_set taskset(pool);
111 for (int i = 0; i < nt; ++i) {
112 taskset.push(pool->push(do_nothing));
113 }
114 taskset.wait();
115 };
116
117 double range;
118 double t = time_trial(func, ntrials, its, &range);
119
120 std::cout << Strutil::sprintf("%2d\t%5.1f launch %8.1f threads/sec\n",
121 nt, t, (nt * its) / t);
122 if (!wedge)
123 break; // don't loop if we're not wedging
124 }
125
126 Benchmarker bench;
127 bench("std::this_thread::get_id()",
128 [=]() { DoNotOptimize(std::this_thread::get_id()); });
129 std::thread::id threadid = std::this_thread::get_id();
130 bench("register/deregister pool worker", [=]() {
131 pool->register_worker(threadid);
132 pool->deregister_worker(threadid);
133 });
134 }
135
136
137
138 int
main(int argc,char ** argv)139 main(int argc, char** argv)
140 {
141 #if !defined(NDEBUG) || defined(OIIO_CI) || defined(OIIO_CODE_COVERAGE)
142 // For the sake of test time, reduce the default iterations for DEBUG,
143 // CI, and code coverage builds. Explicit use of --iters or --trials
144 // will override this, since it comes before the getargs() call.
145 iterations /= 10;
146 ntrials = 1;
147 #endif
148
149 getargs(argc, argv);
150
151 std::cout << "hw threads = " << Sysutil::hardware_concurrency() << "\n";
152
153 time_thread_group();
154 time_thread_pool();
155
156 return unit_test_failures;
157 }
158