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