xref: /aosp_15_r20/external/cronet/base/task/thread_pool/thread_pool_perftest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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