1 // Copyright 2015 The Chromium Authors. All rights reserved.
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/sequence_manager/sequence_manager.h"
6
7 #include <stddef.h>
8 #include <memory>
9
10 #include "base/bind.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/run_loop.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/task/sequence_manager/task_queue_impl.h"
16 #include "base/task/sequence_manager/test/mock_time_domain.h"
17 #include "base/task/sequence_manager/test/sequence_manager_for_test.h"
18 #include "base/task/sequence_manager/test/test_task_queue.h"
19 #include "base/task/sequence_manager/test/test_task_time_observer.h"
20 #include "base/threading/thread.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "base/time/default_tick_clock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "testing/perf/perf_test.h"
25
26 namespace base {
27 namespace sequence_manager {
28
29 // To reduce noise related to the OS timer, we use a mock time domain to
30 // fast forward the timers.
31 class PerfTestTimeDomain : public MockTimeDomain {
32 public:
PerfTestTimeDomain()33 PerfTestTimeDomain() : MockTimeDomain(TimeTicks::Now()) {}
34 ~PerfTestTimeDomain() override = default;
35
DelayTillNextTask(LazyNow * lazy_now)36 Optional<TimeDelta> DelayTillNextTask(LazyNow* lazy_now) override {
37 Optional<TimeTicks> run_time = NextScheduledRunTime();
38 if (!run_time)
39 return nullopt;
40 SetNowTicks(*run_time);
41 // Makes SequenceManager to continue immediately.
42 return TimeDelta();
43 }
44
SetNextDelayedDoWork(LazyNow * lazy_now,TimeTicks run_time)45 void SetNextDelayedDoWork(LazyNow* lazy_now, TimeTicks run_time) override {
46 // De-dupe DoWorks.
47 if (NumberOfScheduledWakeUps() == 1u)
48 RequestDoWork();
49 }
50
51 private:
52 DISALLOW_COPY_AND_ASSIGN(PerfTestTimeDomain);
53 };
54
55 class SequenceManagerPerfTest : public testing::Test {
56 public:
SequenceManagerPerfTest()57 SequenceManagerPerfTest()
58 : num_queues_(0),
59 max_tasks_in_flight_(0),
60 num_tasks_in_flight_(0),
61 num_tasks_to_post_(0),
62 num_tasks_to_run_(0) {}
63
SetUp()64 void SetUp() override {
65 if (ThreadTicks::IsSupported())
66 ThreadTicks::WaitUntilInitialized();
67 }
68
TearDown()69 void TearDown() override {
70 queues_.clear();
71 manager_->UnregisterTimeDomain(time_domain_.get());
72 manager_.reset();
73 }
74
Initialize(size_t num_queues)75 void Initialize(size_t num_queues) {
76 num_queues_ = num_queues;
77 message_loop_.reset(new MessageLoop());
78 manager_ = SequenceManagerForTest::Create(message_loop_.get(),
79 message_loop_->task_runner(),
80 DefaultTickClock::GetInstance());
81 manager_->AddTaskTimeObserver(&test_task_time_observer_);
82
83 time_domain_.reset(new PerfTestTimeDomain());
84 manager_->RegisterTimeDomain(time_domain_.get());
85
86 for (size_t i = 0; i < num_queues; i++) {
87 queues_.push_back(manager_->CreateTaskQueue<TestTaskQueue>(
88 TaskQueue::Spec("test").SetTimeDomain(time_domain_.get())));
89 }
90
91 delayed_task_closure_ = BindRepeating(
92 &SequenceManagerPerfTest::TestDelayedTask, Unretained(this));
93
94 immediate_task_closure_ = BindRepeating(
95 &SequenceManagerPerfTest::TestImmediateTask, Unretained(this));
96 }
97
TestDelayedTask()98 void TestDelayedTask() {
99 if (--num_tasks_to_run_ == 0) {
100 run_loop_->QuitWhenIdle();
101 return;
102 }
103
104 num_tasks_in_flight_--;
105 // NOTE there are only up to max_tasks_in_flight_ pending delayed tasks at
106 // any one time. Thanks to the lower_num_tasks_to_post going to zero if
107 // there are a lot of tasks in flight, the total number of task in flight at
108 // any one time is very variable.
109 unsigned int lower_num_tasks_to_post =
110 num_tasks_in_flight_ < (max_tasks_in_flight_ / 2) ? 1 : 0;
111 unsigned int max_tasks_to_post =
112 num_tasks_to_post_ % 2 ? lower_num_tasks_to_post : 10;
113 for (unsigned int i = 0;
114 i < max_tasks_to_post && num_tasks_in_flight_ < max_tasks_in_flight_ &&
115 num_tasks_to_post_ > 0;
116 i++) {
117 // Choose a queue weighted towards queue 0.
118 unsigned int queue = num_tasks_to_post_ % (num_queues_ + 1);
119 if (queue == num_queues_) {
120 queue = 0;
121 }
122 // Simulate a mix of short and longer delays.
123 unsigned int delay =
124 num_tasks_to_post_ % 2 ? 1 : (10 + num_tasks_to_post_ % 10);
125 queues_[queue]->PostDelayedTask(FROM_HERE, delayed_task_closure_,
126 TimeDelta::FromMilliseconds(delay));
127 num_tasks_in_flight_++;
128 num_tasks_to_post_--;
129 }
130 }
131
TestImmediateTask()132 void TestImmediateTask() {
133 if (--num_tasks_to_run_ == 0) {
134 run_loop_->QuitWhenIdle();
135 return;
136 }
137
138 num_tasks_in_flight_--;
139 // NOTE there are only up to max_tasks_in_flight_ pending delayed tasks at
140 // any one time. Thanks to the lower_num_tasks_to_post going to zero if
141 // there are a lot of tasks in flight, the total number of task in flight at
142 // any one time is very variable.
143 unsigned int lower_num_tasks_to_post =
144 num_tasks_in_flight_ < (max_tasks_in_flight_ / 2) ? 1 : 0;
145 unsigned int max_tasks_to_post =
146 num_tasks_to_post_ % 2 ? lower_num_tasks_to_post : 10;
147 for (unsigned int i = 0;
148 i < max_tasks_to_post && num_tasks_in_flight_ < max_tasks_in_flight_ &&
149 num_tasks_to_post_ > 0;
150 i++) {
151 // Choose a queue weighted towards queue 0.
152 unsigned int queue = num_tasks_to_post_ % (num_queues_ + 1);
153 if (queue == num_queues_) {
154 queue = 0;
155 }
156 queues_[queue]->PostTask(FROM_HERE, immediate_task_closure_);
157 num_tasks_in_flight_++;
158 num_tasks_to_post_--;
159 }
160 }
161
ResetAndCallTestDelayedTask(unsigned int num_tasks_to_run)162 void ResetAndCallTestDelayedTask(unsigned int num_tasks_to_run) {
163 num_tasks_in_flight_ = 1;
164 num_tasks_to_post_ = num_tasks_to_run;
165 num_tasks_to_run_ = num_tasks_to_run;
166 TestDelayedTask();
167 }
168
ResetAndCallTestImmediateTask(unsigned int num_tasks_to_run)169 void ResetAndCallTestImmediateTask(unsigned int num_tasks_to_run) {
170 num_tasks_in_flight_ = 1;
171 num_tasks_to_post_ = num_tasks_to_run;
172 num_tasks_to_run_ = num_tasks_to_run;
173 TestImmediateTask();
174 }
175
Benchmark(const std::string & trace,const RepeatingClosure & test_task)176 void Benchmark(const std::string& trace, const RepeatingClosure& test_task) {
177 ThreadTicks start = ThreadTicks::Now();
178 ThreadTicks now;
179 unsigned long long num_iterations = 0;
180 do {
181 test_task.Run();
182 run_loop_.reset(new RunLoop());
183 run_loop_->Run();
184 now = ThreadTicks::Now();
185 num_iterations++;
186 } while (now - start < TimeDelta::FromSeconds(5));
187 perf_test::PrintResult(
188 "task", "", trace,
189 (now - start).InMicroseconds() / static_cast<double>(num_iterations),
190 "us/run", true);
191 }
192
193 size_t num_queues_;
194 unsigned int max_tasks_in_flight_;
195 unsigned int num_tasks_in_flight_;
196 unsigned int num_tasks_to_post_;
197 unsigned int num_tasks_to_run_;
198 std::unique_ptr<MessageLoop> message_loop_;
199 std::unique_ptr<SequenceManager> manager_;
200 std::unique_ptr<RunLoop> run_loop_;
201 std::unique_ptr<TimeDomain> time_domain_;
202 std::vector<scoped_refptr<SingleThreadTaskRunner>> queues_;
203 RepeatingClosure delayed_task_closure_;
204 RepeatingClosure immediate_task_closure_;
205 // TODO(alexclarke): parameterize so we can measure with and without a
206 // TaskTimeObserver.
207 TestTaskTimeObserver test_task_time_observer_;
208 };
209
TEST_F(SequenceManagerPerfTest,RunTenThousandDelayedTasks_OneQueue)210 TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_OneQueue) {
211 if (!ThreadTicks::IsSupported())
212 return;
213 Initialize(1u);
214
215 max_tasks_in_flight_ = 200;
216 Benchmark("run 10000 delayed tasks with one queue",
217 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
218 Unretained(this), 10000));
219 }
220
TEST_F(SequenceManagerPerfTest,RunTenThousandDelayedTasks_FourQueues)221 TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_FourQueues) {
222 if (!ThreadTicks::IsSupported())
223 return;
224 Initialize(4u);
225
226 max_tasks_in_flight_ = 200;
227 Benchmark("run 10000 delayed tasks with four queues",
228 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
229 Unretained(this), 10000));
230 }
231
TEST_F(SequenceManagerPerfTest,RunTenThousandDelayedTasks_EightQueues)232 TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_EightQueues) {
233 if (!ThreadTicks::IsSupported())
234 return;
235 Initialize(8u);
236
237 max_tasks_in_flight_ = 200;
238 Benchmark("run 10000 delayed tasks with eight queues",
239 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
240 Unretained(this), 10000));
241 }
242
TEST_F(SequenceManagerPerfTest,RunTenThousandDelayedTasks_ThirtyTwoQueues)243 TEST_F(SequenceManagerPerfTest, RunTenThousandDelayedTasks_ThirtyTwoQueues) {
244 if (!ThreadTicks::IsSupported())
245 return;
246 Initialize(32u);
247
248 max_tasks_in_flight_ = 200;
249 Benchmark("run 10000 delayed tasks with thirty two queues",
250 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestDelayedTask,
251 Unretained(this), 10000));
252 }
253
TEST_F(SequenceManagerPerfTest,RunTenThousandImmediateTasks_OneQueue)254 TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_OneQueue) {
255 if (!ThreadTicks::IsSupported())
256 return;
257 Initialize(1u);
258
259 max_tasks_in_flight_ = 200;
260 Benchmark(
261 "run 10000 immediate tasks with one queue",
262 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
263 Unretained(this), 10000));
264 }
265
TEST_F(SequenceManagerPerfTest,RunTenThousandImmediateTasks_FourQueues)266 TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_FourQueues) {
267 if (!ThreadTicks::IsSupported())
268 return;
269 Initialize(4u);
270
271 max_tasks_in_flight_ = 200;
272 Benchmark(
273 "run 10000 immediate tasks with four queues",
274 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
275 Unretained(this), 10000));
276 }
277
TEST_F(SequenceManagerPerfTest,RunTenThousandImmediateTasks_EightQueues)278 TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_EightQueues) {
279 if (!ThreadTicks::IsSupported())
280 return;
281 Initialize(8u);
282
283 max_tasks_in_flight_ = 200;
284 Benchmark(
285 "run 10000 immediate tasks with eight queues",
286 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
287 Unretained(this), 10000));
288 }
289
TEST_F(SequenceManagerPerfTest,RunTenThousandImmediateTasks_ThirtyTwoQueues)290 TEST_F(SequenceManagerPerfTest, RunTenThousandImmediateTasks_ThirtyTwoQueues) {
291 if (!ThreadTicks::IsSupported())
292 return;
293 Initialize(32u);
294
295 max_tasks_in_flight_ = 200;
296 Benchmark(
297 "run 10000 immediate tasks with thirty two queues",
298 BindRepeating(&SequenceManagerPerfTest::ResetAndCallTestImmediateTask,
299 Unretained(this), 10000));
300 }
301
302 // TODO(alexclarke): Add additional tests with different mixes of non-delayed vs
303 // delayed tasks.
304
305 } // namespace sequence_manager
306 } // namespace base
307