1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/task/thread_pool.h"
6
7 #include <stddef.h>
8
9 #include <atomic>
10 #include <memory>
11 #include <optional>
12 #include <vector>
13
14 #include "base/barrier_closure.h"
15 #include "base/functional/bind.h"
16 #include "base/functional/callback.h"
17 #include "base/functional/callback_helpers.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/synchronization/waitable_event.h"
20 #include "base/task/thread_pool/thread_pool_instance.h"
21 #include "base/threading/simple_thread.h"
22 #include "base/time/time.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "testing/perf/perf_result_reporter.h"
25
26 namespace base {
27 namespace internal {
28
29 namespace {
30
31 constexpr char kMetricPrefixThreadPool[] = "ThreadPool.";
32 constexpr char kMetricPostTaskThroughput[] = "post_task_throughput";
33 constexpr char kMetricRunTaskThroughput[] = "run_task_throughput";
34 constexpr char kMetricNumTasksPosted[] = "num_tasks_posted";
35 constexpr char kStoryBindPostThenRunNoOp[] = "bind_post_then_run_noop_tasks";
36 constexpr char kStoryPostThenRunNoOp[] = "post_then_run_noop_tasks";
37 constexpr char kStoryPostThenRunNoOpManyThreads[] =
38 "post_then_run_noop_tasks_many_threads";
39 constexpr char kStoryPostThenRunNoOpMoreThanRunningThreads[] =
40 "post_then_run_noop_tasks_more_than_running_threads";
41 constexpr char kStoryPostRunNoOp[] = "post_run_noop_tasks";
42 constexpr char kStoryPostRunNoOpManyThreads[] =
43 "post_run_noop_tasks_many_threads";
44 constexpr char kStoryPostRunBusyManyThreads[] =
45 "post_run_busy_tasks_many_threads";
46
SetUpReporter(const std::string & story_name)47 perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
48 perf_test::PerfResultReporter reporter(kMetricPrefixThreadPool, story_name);
49 reporter.RegisterImportantMetric(kMetricPostTaskThroughput, "runs/s");
50 reporter.RegisterImportantMetric(kMetricRunTaskThroughput, "runs/s");
51 reporter.RegisterImportantMetric(kMetricNumTasksPosted, "count");
52 return reporter;
53 }
54
55 enum class ExecutionMode {
56 // Allows tasks to start running while tasks are being posted by posting
57 // threads.
58 kPostAndRun,
59 // Uses an execution fence to wait for all posting threads to be done before
60 // running tasks that were posted.
61 kPostThenRun,
62 };
63
64 // A thread that waits for the caller to signal an event before proceeding to
65 // call action.Run().
66 class PostingThread : public SimpleThread {
67 public:
68 // Creates a PostingThread that waits on |start_event| before calling
69 // action.Run().
PostingThread(WaitableEvent * start_event,base::OnceClosure action,base::OnceClosure completion)70 PostingThread(WaitableEvent* start_event,
71 base::OnceClosure action,
72 base::OnceClosure completion)
73 : SimpleThread("PostingThread"),
74 start_event_(start_event),
75 action_(std::move(action)),
76 completion_(std::move(completion)) {
77 Start();
78 }
79 PostingThread(const PostingThread&) = delete;
80 PostingThread& operator=(const PostingThread&) = delete;
81
Run()82 void Run() override {
83 start_event_->Wait();
84 std::move(action_).Run();
85 std::move(completion_).Run();
86 }
87
88 private:
89 const raw_ptr<WaitableEvent> start_event_;
90 base::OnceClosure action_;
91 base::OnceClosure completion_;
92 };
93
94 class ThreadPoolPerfTest : public testing::Test {
95 public:
96 ThreadPoolPerfTest(const ThreadPoolPerfTest&) = delete;
97 ThreadPoolPerfTest& operator=(const ThreadPoolPerfTest&) = delete;
98
99 // Posting actions:
100
ContinuouslyBindAndPostNoOpTasks(size_t num_tasks)101 void ContinuouslyBindAndPostNoOpTasks(size_t num_tasks) {
102 scoped_refptr<TaskRunner> task_runner = ThreadPool::CreateTaskRunner({});
103 for (size_t i = 0; i < num_tasks; ++i) {
104 ++num_tasks_pending_;
105 ++num_posted_tasks_;
106 task_runner->PostTask(FROM_HERE,
107 base::BindOnce(
108 [](std::atomic_size_t* num_task_pending) {
109 (*num_task_pending)--;
110 },
111 &num_tasks_pending_));
112 }
113 }
114
ContinuouslyPostNoOpTasks(size_t num_tasks)115 void ContinuouslyPostNoOpTasks(size_t num_tasks) {
116 scoped_refptr<TaskRunner> task_runner = ThreadPool::CreateTaskRunner({});
117 base::RepeatingClosure closure = base::BindRepeating(
118 [](std::atomic_size_t* num_task_pending) { (*num_task_pending)--; },
119 &num_tasks_pending_);
120 for (size_t i = 0; i < num_tasks; ++i) {
121 ++num_tasks_pending_;
122 ++num_posted_tasks_;
123 task_runner->PostTask(FROM_HERE, closure);
124 }
125 }
126
ContinuouslyPostBusyWaitTasks(size_t num_tasks,base::TimeDelta duration)127 void ContinuouslyPostBusyWaitTasks(size_t num_tasks,
128 base::TimeDelta duration) {
129 scoped_refptr<TaskRunner> task_runner = ThreadPool::CreateTaskRunner({});
130 base::RepeatingClosure closure = base::BindRepeating(
131 [](std::atomic_size_t* num_task_pending, base::TimeDelta duration) {
132 base::TimeTicks end_time = base::TimeTicks::Now() + duration;
133 while (base::TimeTicks::Now() < end_time) {
134 }
135 (*num_task_pending)--;
136 },
137 Unretained(&num_tasks_pending_), duration);
138 for (size_t i = 0; i < num_tasks; ++i) {
139 ++num_tasks_pending_;
140 ++num_posted_tasks_;
141 task_runner->PostTask(FROM_HERE, closure);
142 }
143 }
144
145 protected:
ThreadPoolPerfTest()146 ThreadPoolPerfTest() { ThreadPoolInstance::Create("PerfTest"); }
147
~ThreadPoolPerfTest()148 ~ThreadPoolPerfTest() override { ThreadPoolInstance::Set(nullptr); }
149
StartThreadPool(size_t num_running_threads,size_t num_posting_threads,base::RepeatingClosure post_action)150 void StartThreadPool(size_t num_running_threads,
151 size_t num_posting_threads,
152 base::RepeatingClosure post_action) {
153 ThreadPoolInstance::Get()->Start({num_running_threads});
154
155 base::RepeatingClosure done = BarrierClosure(
156 num_posting_threads,
157 base::BindOnce(&ThreadPoolPerfTest::OnCompletePostingTasks,
158 base::Unretained(this)));
159
160 for (size_t i = 0; i < num_posting_threads; ++i) {
161 threads_.emplace_back(std::make_unique<PostingThread>(
162 &start_posting_tasks_, post_action, done));
163 }
164 }
165
OnCompletePostingTasks()166 void OnCompletePostingTasks() { complete_posting_tasks_.Signal(); }
167
Benchmark(const std::string & story_name,ExecutionMode execution_mode)168 void Benchmark(const std::string& story_name, ExecutionMode execution_mode) {
169 std::optional<ThreadPoolInstance::ScopedExecutionFence> execution_fence;
170 if (execution_mode == ExecutionMode::kPostThenRun) {
171 execution_fence.emplace();
172 }
173 TimeTicks tasks_run_start = TimeTicks::Now();
174 start_posting_tasks_.Signal();
175 complete_posting_tasks_.Wait();
176 post_task_duration_ = TimeTicks::Now() - tasks_run_start;
177
178 if (execution_mode == ExecutionMode::kPostThenRun) {
179 tasks_run_start = TimeTicks::Now();
180 execution_fence.reset();
181 }
182
183 // Wait for no pending tasks.
184 ThreadPoolInstance::Get()->FlushForTesting();
185 tasks_run_duration_ = TimeTicks::Now() - tasks_run_start;
186 ASSERT_EQ(0U, num_tasks_pending_);
187
188 for (auto& thread : threads_)
189 thread->Join();
190 ThreadPoolInstance::Get()->JoinForTesting();
191
192 auto reporter = SetUpReporter(story_name);
193 reporter.AddResult(
194 kMetricPostTaskThroughput,
195 num_posted_tasks_ /
196 static_cast<double>(post_task_duration_.InSecondsF()));
197 reporter.AddResult(
198 kMetricRunTaskThroughput,
199 num_posted_tasks_ /
200 static_cast<double>(tasks_run_duration_.InSecondsF()));
201 reporter.AddResult(kMetricNumTasksPosted, num_posted_tasks_);
202 }
203
204 private:
205 WaitableEvent start_posting_tasks_;
206 WaitableEvent complete_posting_tasks_;
207
208 TimeDelta post_task_duration_;
209 TimeDelta tasks_run_duration_;
210
211 std::atomic_size_t num_tasks_pending_{0};
212 std::atomic_size_t num_posted_tasks_{0};
213
214 std::vector<std::unique_ptr<PostingThread>> threads_;
215 };
216
217 } // namespace
218
TEST_F(ThreadPoolPerfTest,BindPostThenRunNoOpTasks)219 TEST_F(ThreadPoolPerfTest, BindPostThenRunNoOpTasks) {
220 StartThreadPool(
221 1, 1,
222 BindRepeating(&ThreadPoolPerfTest::ContinuouslyBindAndPostNoOpTasks,
223 Unretained(this), 10000));
224 Benchmark(kStoryBindPostThenRunNoOp, ExecutionMode::kPostThenRun);
225 }
226
TEST_F(ThreadPoolPerfTest,PostThenRunNoOpTasks)227 TEST_F(ThreadPoolPerfTest, PostThenRunNoOpTasks) {
228 StartThreadPool(1, 1,
229 BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks,
230 Unretained(this), 10000));
231 Benchmark(kStoryPostThenRunNoOp, ExecutionMode::kPostThenRun);
232 }
233
TEST_F(ThreadPoolPerfTest,PostThenRunNoOpTasksManyThreads)234 TEST_F(ThreadPoolPerfTest, PostThenRunNoOpTasksManyThreads) {
235 StartThreadPool(4, 4,
236 BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks,
237 Unretained(this), 10000));
238 Benchmark(kStoryPostThenRunNoOpManyThreads, ExecutionMode::kPostThenRun);
239 }
240
TEST_F(ThreadPoolPerfTest,PostThenRunNoOpTasksMorePostingThanRunningThreads)241 TEST_F(ThreadPoolPerfTest, PostThenRunNoOpTasksMorePostingThanRunningThreads) {
242 StartThreadPool(1, 4,
243 BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks,
244 Unretained(this), 10000));
245 Benchmark(kStoryPostThenRunNoOpMoreThanRunningThreads,
246 ExecutionMode::kPostThenRun);
247 }
248
TEST_F(ThreadPoolPerfTest,PostRunNoOpTasks)249 TEST_F(ThreadPoolPerfTest, PostRunNoOpTasks) {
250 StartThreadPool(1, 1,
251 BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks,
252 Unretained(this), 10000));
253 Benchmark(kStoryPostRunNoOp, ExecutionMode::kPostAndRun);
254 }
255
TEST_F(ThreadPoolPerfTest,PostRunNoOpTasksManyThreads)256 TEST_F(ThreadPoolPerfTest, PostRunNoOpTasksManyThreads) {
257 StartThreadPool(4, 4,
258 BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostNoOpTasks,
259 Unretained(this), 10000));
260 Benchmark(kStoryPostRunNoOpManyThreads, ExecutionMode::kPostAndRun);
261 }
262
TEST_F(ThreadPoolPerfTest,PostRunBusyTasksManyThreads)263 TEST_F(ThreadPoolPerfTest, PostRunBusyTasksManyThreads) {
264 StartThreadPool(
265 4, 4,
266 BindRepeating(&ThreadPoolPerfTest::ContinuouslyPostBusyWaitTasks,
267 Unretained(this), 10000, base::Microseconds(200)));
268 Benchmark(kStoryPostRunBusyManyThreads, ExecutionMode::kPostAndRun);
269 }
270
271 } // namespace internal
272 } // namespace base
273