• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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