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