xref: /aosp_15_r20/external/cronet/base/task/job_perftest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker // Copyright 2020 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker 
5*6777b538SAndroid Build Coastguard Worker #include <stddef.h>
6*6777b538SAndroid Build Coastguard Worker 
7*6777b538SAndroid Build Coastguard Worker #include <atomic>
8*6777b538SAndroid Build Coastguard Worker #include <optional>
9*6777b538SAndroid Build Coastguard Worker #include <utility>
10*6777b538SAndroid Build Coastguard Worker #include <vector>
11*6777b538SAndroid Build Coastguard Worker 
12*6777b538SAndroid Build Coastguard Worker #include "base/containers/queue.h"
13*6777b538SAndroid Build Coastguard Worker #include "base/containers/stack.h"
14*6777b538SAndroid Build Coastguard Worker #include "base/functional/callback_helpers.h"
15*6777b538SAndroid Build Coastguard Worker #include "base/synchronization/lock.h"
16*6777b538SAndroid Build Coastguard Worker #include "base/task/post_job.h"
17*6777b538SAndroid Build Coastguard Worker #include "base/task/thread_pool.h"
18*6777b538SAndroid Build Coastguard Worker #include "base/test/bind.h"
19*6777b538SAndroid Build Coastguard Worker #include "base/test/task_environment.h"
20*6777b538SAndroid Build Coastguard Worker #include "testing/gtest/include/gtest/gtest.h"
21*6777b538SAndroid Build Coastguard Worker #include "testing/perf/perf_result_reporter.h"
22*6777b538SAndroid Build Coastguard Worker 
23*6777b538SAndroid Build Coastguard Worker namespace base {
24*6777b538SAndroid Build Coastguard Worker 
25*6777b538SAndroid Build Coastguard Worker namespace {
26*6777b538SAndroid Build Coastguard Worker 
27*6777b538SAndroid Build Coastguard Worker // The perftest implements the following assignment strategy:
28*6777b538SAndroid Build Coastguard Worker // - Naive: See RunJobWithNaiveAssignment().
29*6777b538SAndroid Build Coastguard Worker // - Dynamic: See RunJobWithDynamicAssignment().
30*6777b538SAndroid Build Coastguard Worker // - Loop around: See RunJobWithLoopAround().
31*6777b538SAndroid Build Coastguard Worker // The following test setups exists for different strategies, although
32*6777b538SAndroid Build Coastguard Worker // not every combination is performed:
33*6777b538SAndroid Build Coastguard Worker // - No-op: Work items are no-op tasks.
34*6777b538SAndroid Build Coastguard Worker // - No-op + disrupted: 10 disruptive tasks are posted every 1ms.
35*6777b538SAndroid Build Coastguard Worker // - Busy wait: Work items are busy wait for 5us.
36*6777b538SAndroid Build Coastguard Worker // - Busy wait + disrupted
37*6777b538SAndroid Build Coastguard Worker 
38*6777b538SAndroid Build Coastguard Worker constexpr char kMetricPrefixJob[] = "Job.";
39*6777b538SAndroid Build Coastguard Worker constexpr char kMetricWorkThroughput[] = "work_throughput";
40*6777b538SAndroid Build Coastguard Worker constexpr char kStoryNoOpNaive[] = "noop_naive";
41*6777b538SAndroid Build Coastguard Worker constexpr char kStoryBusyWaitNaive[] = "busy_wait_naive";
42*6777b538SAndroid Build Coastguard Worker constexpr char kStoryNoOpAtomic[] = "noop_atomic";
43*6777b538SAndroid Build Coastguard Worker constexpr char kStoryNoOpAtomicDisrupted[] = "noop_atomic_disrupted";
44*6777b538SAndroid Build Coastguard Worker constexpr char kStoryBusyWaitAtomic[] = "busy_wait_atomic";
45*6777b538SAndroid Build Coastguard Worker constexpr char kStoryBusyWaitAtomicDisrupted[] = "busy_wait_atomic_disrupted";
46*6777b538SAndroid Build Coastguard Worker constexpr char kStoryNoOpDynamic[] = "noop_dynamic";
47*6777b538SAndroid Build Coastguard Worker constexpr char kStoryNoOpDynamicDisrupted[] = "noop_dynamic_disrupted";
48*6777b538SAndroid Build Coastguard Worker constexpr char kStoryBusyWaitDynamic[] = "busy_wait_dynamic";
49*6777b538SAndroid Build Coastguard Worker constexpr char kStoryBusyWaitDynamicDisrupted[] = "busy_wait_dynamic_disrupted";
50*6777b538SAndroid Build Coastguard Worker constexpr char kStoryNoOpLoopAround[] = "noop_loop_around";
51*6777b538SAndroid Build Coastguard Worker constexpr char kStoryNoOpLoopAroundDisrupted[] = "noop_loop_around_disrupted";
52*6777b538SAndroid Build Coastguard Worker constexpr char kStoryBusyWaitLoopAround[] = "busy_wait_loop_around";
53*6777b538SAndroid Build Coastguard Worker constexpr char kStoryBusyWaitLoopAroundDisrupted[] =
54*6777b538SAndroid Build Coastguard Worker     "busy_wait_loop_around_disrupted";
55*6777b538SAndroid Build Coastguard Worker 
SetUpReporter(const std::string & story_name)56*6777b538SAndroid Build Coastguard Worker perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
57*6777b538SAndroid Build Coastguard Worker   perf_test::PerfResultReporter reporter(kMetricPrefixJob, story_name);
58*6777b538SAndroid Build Coastguard Worker   reporter.RegisterImportantMetric(kMetricWorkThroughput, "tasks/ms");
59*6777b538SAndroid Build Coastguard Worker   return reporter;
60*6777b538SAndroid Build Coastguard Worker }
61*6777b538SAndroid Build Coastguard Worker 
62*6777b538SAndroid Build Coastguard Worker // A thread-safe data structure that generates heuristic starting points in a
63*6777b538SAndroid Build Coastguard Worker // range to process items in parallel.
64*6777b538SAndroid Build Coastguard Worker // Note: we could expose this atomic-binary-search-index-generator in
65*6777b538SAndroid Build Coastguard Worker // //base/util if it's useful for real-world use cases.
66*6777b538SAndroid Build Coastguard Worker class IndexGenerator {
67*6777b538SAndroid Build Coastguard Worker  public:
IndexGenerator(size_t size)68*6777b538SAndroid Build Coastguard Worker   explicit IndexGenerator(size_t size) : size_(size) {
69*6777b538SAndroid Build Coastguard Worker     AutoLock auto_lock(lock_);
70*6777b538SAndroid Build Coastguard Worker     pending_indices_.push(0);
71*6777b538SAndroid Build Coastguard Worker     ranges_to_split_.push({0, size_});
72*6777b538SAndroid Build Coastguard Worker   }
73*6777b538SAndroid Build Coastguard Worker 
74*6777b538SAndroid Build Coastguard Worker   IndexGenerator(const IndexGenerator&) = delete;
75*6777b538SAndroid Build Coastguard Worker   IndexGenerator& operator=(const IndexGenerator&) = delete;
76*6777b538SAndroid Build Coastguard Worker 
GetNext()77*6777b538SAndroid Build Coastguard Worker   std::optional<size_t> GetNext() {
78*6777b538SAndroid Build Coastguard Worker     AutoLock auto_lock(lock_);
79*6777b538SAndroid Build Coastguard Worker     if (!pending_indices_.empty()) {
80*6777b538SAndroid Build Coastguard Worker       // Return any pending index first.
81*6777b538SAndroid Build Coastguard Worker       auto index = pending_indices_.top();
82*6777b538SAndroid Build Coastguard Worker       pending_indices_.pop();
83*6777b538SAndroid Build Coastguard Worker       return index;
84*6777b538SAndroid Build Coastguard Worker     }
85*6777b538SAndroid Build Coastguard Worker     if (ranges_to_split_.empty())
86*6777b538SAndroid Build Coastguard Worker       return std::nullopt;
87*6777b538SAndroid Build Coastguard Worker 
88*6777b538SAndroid Build Coastguard Worker     // Split the oldest running range in 2 and return the middle index as
89*6777b538SAndroid Build Coastguard Worker     // starting point.
90*6777b538SAndroid Build Coastguard Worker     auto range = ranges_to_split_.front();
91*6777b538SAndroid Build Coastguard Worker     ranges_to_split_.pop();
92*6777b538SAndroid Build Coastguard Worker     size_t size = range.second - range.first;
93*6777b538SAndroid Build Coastguard Worker     size_t mid = range.first + size / 2;
94*6777b538SAndroid Build Coastguard Worker     // Both sides of the range are added to |ranges_to_split_| so they may be
95*6777b538SAndroid Build Coastguard Worker     // further split if possible.
96*6777b538SAndroid Build Coastguard Worker     if (mid - range.first > 1)
97*6777b538SAndroid Build Coastguard Worker       ranges_to_split_.push({range.first, mid});
98*6777b538SAndroid Build Coastguard Worker     if (range.second - mid > 1)
99*6777b538SAndroid Build Coastguard Worker       ranges_to_split_.push({mid, range.second});
100*6777b538SAndroid Build Coastguard Worker     return mid;
101*6777b538SAndroid Build Coastguard Worker   }
102*6777b538SAndroid Build Coastguard Worker 
GiveBack(size_t index)103*6777b538SAndroid Build Coastguard Worker   void GiveBack(size_t index) {
104*6777b538SAndroid Build Coastguard Worker     AutoLock auto_lock(lock_);
105*6777b538SAndroid Build Coastguard Worker     // Add |index| to pending indices so GetNext() may return it before anything
106*6777b538SAndroid Build Coastguard Worker     // else.
107*6777b538SAndroid Build Coastguard Worker     pending_indices_.push(index);
108*6777b538SAndroid Build Coastguard Worker   }
109*6777b538SAndroid Build Coastguard Worker 
110*6777b538SAndroid Build Coastguard Worker  private:
111*6777b538SAndroid Build Coastguard Worker   base::Lock lock_;
112*6777b538SAndroid Build Coastguard Worker   // Pending indices that are ready to be handed out, prioritized over
113*6777b538SAndroid Build Coastguard Worker   // |pending_ranges_| when non-empty.
114*6777b538SAndroid Build Coastguard Worker   base::stack<size_t> pending_indices_ GUARDED_BY(lock_);
115*6777b538SAndroid Build Coastguard Worker   // Pending [start, end] (exclusive) ranges to split and hand out indices from.
116*6777b538SAndroid Build Coastguard Worker   base::queue<std::pair<size_t, size_t>> ranges_to_split_ GUARDED_BY(lock_);
117*6777b538SAndroid Build Coastguard Worker   const size_t size_;
118*6777b538SAndroid Build Coastguard Worker };
119*6777b538SAndroid Build Coastguard Worker 
120*6777b538SAndroid Build Coastguard Worker struct WorkItem {
121*6777b538SAndroid Build Coastguard Worker   std::atomic_bool acquire{false};
122*6777b538SAndroid Build Coastguard Worker 
TryAcquirebase::__anon8b4b63c80111::WorkItem123*6777b538SAndroid Build Coastguard Worker   bool TryAcquire() {
124*6777b538SAndroid Build Coastguard Worker     // memory_order_relaxed is sufficient as the WorkItem's state itself hasn't
125*6777b538SAndroid Build Coastguard Worker     // been modified since the beginning of its associated job. This is only
126*6777b538SAndroid Build Coastguard Worker     // atomically acquiring the right to work on it.
127*6777b538SAndroid Build Coastguard Worker     return acquire.exchange(true, std::memory_order_relaxed) == false;
128*6777b538SAndroid Build Coastguard Worker   }
129*6777b538SAndroid Build Coastguard Worker };
130*6777b538SAndroid Build Coastguard Worker 
131*6777b538SAndroid Build Coastguard Worker class WorkList {
132*6777b538SAndroid Build Coastguard Worker  public:
WorkList(size_t num_work_items,RepeatingCallback<void (size_t)> process_item)133*6777b538SAndroid Build Coastguard Worker   WorkList(size_t num_work_items, RepeatingCallback<void(size_t)> process_item)
134*6777b538SAndroid Build Coastguard Worker       : num_incomplete_items_(num_work_items),
135*6777b538SAndroid Build Coastguard Worker         items_(num_work_items),
136*6777b538SAndroid Build Coastguard Worker         process_item_(std::move(process_item)) {}
137*6777b538SAndroid Build Coastguard Worker 
138*6777b538SAndroid Build Coastguard Worker   WorkList(const WorkList&) = delete;
139*6777b538SAndroid Build Coastguard Worker   WorkList& operator=(const WorkList&) = delete;
140*6777b538SAndroid Build Coastguard Worker 
141*6777b538SAndroid Build Coastguard Worker   // Acquires work item at |index|. Returns true if successful, or false if the
142*6777b538SAndroid Build Coastguard Worker   // item was already acquired.
TryAcquire(size_t index)143*6777b538SAndroid Build Coastguard Worker   bool TryAcquire(size_t index) { return items_[index].TryAcquire(); }
144*6777b538SAndroid Build Coastguard Worker 
145*6777b538SAndroid Build Coastguard Worker   // Processes work item at |index|. Returns true if there are more work items
146*6777b538SAndroid Build Coastguard Worker   // to process, or false if all items were processed.
ProcessWorkItem(size_t index)147*6777b538SAndroid Build Coastguard Worker   bool ProcessWorkItem(size_t index) {
148*6777b538SAndroid Build Coastguard Worker     process_item_.Run(index);
149*6777b538SAndroid Build Coastguard Worker     return num_incomplete_items_.fetch_sub(1, std::memory_order_relaxed) > 1;
150*6777b538SAndroid Build Coastguard Worker   }
151*6777b538SAndroid Build Coastguard Worker 
NumIncompleteWorkItems(size_t) const152*6777b538SAndroid Build Coastguard Worker   size_t NumIncompleteWorkItems(size_t /*worker_count*/) const {
153*6777b538SAndroid Build Coastguard Worker     // memory_order_relaxed is sufficient since this is not synchronized with
154*6777b538SAndroid Build Coastguard Worker     // other state.
155*6777b538SAndroid Build Coastguard Worker     return num_incomplete_items_.load(std::memory_order_relaxed);
156*6777b538SAndroid Build Coastguard Worker   }
157*6777b538SAndroid Build Coastguard Worker 
NumWorkItems() const158*6777b538SAndroid Build Coastguard Worker   size_t NumWorkItems() const { return items_.size(); }
159*6777b538SAndroid Build Coastguard Worker 
160*6777b538SAndroid Build Coastguard Worker  private:
161*6777b538SAndroid Build Coastguard Worker   std::atomic_size_t num_incomplete_items_;
162*6777b538SAndroid Build Coastguard Worker   std::vector<WorkItem> items_;
163*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> process_item_;
164*6777b538SAndroid Build Coastguard Worker };
165*6777b538SAndroid Build Coastguard Worker 
BusyWaitCallback(TimeDelta delta)166*6777b538SAndroid Build Coastguard Worker RepeatingCallback<void(size_t)> BusyWaitCallback(TimeDelta delta) {
167*6777b538SAndroid Build Coastguard Worker   return base::BindRepeating(
168*6777b538SAndroid Build Coastguard Worker       [](base::TimeDelta duration, size_t index) {
169*6777b538SAndroid Build Coastguard Worker         const base::TimeTicks end_time = base::TimeTicks::Now() + duration;
170*6777b538SAndroid Build Coastguard Worker         while (base::TimeTicks::Now() < end_time)
171*6777b538SAndroid Build Coastguard Worker           ;
172*6777b538SAndroid Build Coastguard Worker       },
173*6777b538SAndroid Build Coastguard Worker       delta);
174*6777b538SAndroid Build Coastguard Worker }
175*6777b538SAndroid Build Coastguard Worker 
176*6777b538SAndroid Build Coastguard Worker // Posts |task_count| no-op tasks every |delay|.
DisruptivePostTasks(size_t task_count,TimeDelta delay)177*6777b538SAndroid Build Coastguard Worker void DisruptivePostTasks(size_t task_count, TimeDelta delay) {
178*6777b538SAndroid Build Coastguard Worker   for (size_t i = 0; i < task_count; ++i) {
179*6777b538SAndroid Build Coastguard Worker     ThreadPool::PostTask(FROM_HERE, {TaskPriority::USER_BLOCKING}, DoNothing());
180*6777b538SAndroid Build Coastguard Worker   }
181*6777b538SAndroid Build Coastguard Worker   ThreadPool::PostDelayedTask(FROM_HERE, {TaskPriority::USER_BLOCKING},
182*6777b538SAndroid Build Coastguard Worker                               BindOnce(&DisruptivePostTasks, task_count, delay),
183*6777b538SAndroid Build Coastguard Worker                               delay);
184*6777b538SAndroid Build Coastguard Worker }
185*6777b538SAndroid Build Coastguard Worker 
186*6777b538SAndroid Build Coastguard Worker class JobPerfTest : public testing::Test {
187*6777b538SAndroid Build Coastguard Worker  public:
188*6777b538SAndroid Build Coastguard Worker   JobPerfTest() = default;
189*6777b538SAndroid Build Coastguard Worker 
190*6777b538SAndroid Build Coastguard Worker   JobPerfTest(const JobPerfTest&) = delete;
191*6777b538SAndroid Build Coastguard Worker   JobPerfTest& operator=(const JobPerfTest&) = delete;
192*6777b538SAndroid Build Coastguard Worker 
193*6777b538SAndroid Build Coastguard Worker   // Process |num_work_items| items with |process_item| in parallel. Work is
194*6777b538SAndroid Build Coastguard Worker   // assigned by having each worker sequentially traversing all items and
195*6777b538SAndroid Build Coastguard Worker   // acquiring unvisited ones.
RunJobWithNaiveAssignment(const std::string & story_name,size_t num_work_items,RepeatingCallback<void (size_t)> process_item)196*6777b538SAndroid Build Coastguard Worker   void RunJobWithNaiveAssignment(const std::string& story_name,
197*6777b538SAndroid Build Coastguard Worker                                  size_t num_work_items,
198*6777b538SAndroid Build Coastguard Worker                                  RepeatingCallback<void(size_t)> process_item) {
199*6777b538SAndroid Build Coastguard Worker     WorkList work_list(num_work_items, std::move(process_item));
200*6777b538SAndroid Build Coastguard Worker 
201*6777b538SAndroid Build Coastguard Worker     const TimeTicks job_run_start = TimeTicks::Now();
202*6777b538SAndroid Build Coastguard Worker 
203*6777b538SAndroid Build Coastguard Worker     WaitableEvent complete;
204*6777b538SAndroid Build Coastguard Worker     auto handle = PostJob(
205*6777b538SAndroid Build Coastguard Worker         FROM_HERE, {TaskPriority::USER_VISIBLE},
206*6777b538SAndroid Build Coastguard Worker         BindRepeating(
207*6777b538SAndroid Build Coastguard Worker             [](WorkList* work_list, WaitableEvent* complete,
208*6777b538SAndroid Build Coastguard Worker                JobDelegate* delegate) {
209*6777b538SAndroid Build Coastguard Worker               for (size_t i = 0; i < work_list->NumWorkItems() &&
210*6777b538SAndroid Build Coastguard Worker                                  work_list->NumIncompleteWorkItems(0) != 0 &&
211*6777b538SAndroid Build Coastguard Worker                                  !delegate->ShouldYield();
212*6777b538SAndroid Build Coastguard Worker                    ++i) {
213*6777b538SAndroid Build Coastguard Worker                 if (!work_list->TryAcquire(i))
214*6777b538SAndroid Build Coastguard Worker                   continue;
215*6777b538SAndroid Build Coastguard Worker                 if (!work_list->ProcessWorkItem(i)) {
216*6777b538SAndroid Build Coastguard Worker                   complete->Signal();
217*6777b538SAndroid Build Coastguard Worker                   return;
218*6777b538SAndroid Build Coastguard Worker                 }
219*6777b538SAndroid Build Coastguard Worker               }
220*6777b538SAndroid Build Coastguard Worker             },
221*6777b538SAndroid Build Coastguard Worker             Unretained(&work_list), Unretained(&complete)),
222*6777b538SAndroid Build Coastguard Worker         BindRepeating(&WorkList::NumIncompleteWorkItems,
223*6777b538SAndroid Build Coastguard Worker                       Unretained(&work_list)));
224*6777b538SAndroid Build Coastguard Worker 
225*6777b538SAndroid Build Coastguard Worker     complete.Wait();
226*6777b538SAndroid Build Coastguard Worker     handle.Join();
227*6777b538SAndroid Build Coastguard Worker     const TimeDelta job_duration = TimeTicks::Now() - job_run_start;
228*6777b538SAndroid Build Coastguard Worker     EXPECT_EQ(0U, work_list.NumIncompleteWorkItems(0));
229*6777b538SAndroid Build Coastguard Worker 
230*6777b538SAndroid Build Coastguard Worker     auto reporter = SetUpReporter(story_name);
231*6777b538SAndroid Build Coastguard Worker     reporter.AddResult(kMetricWorkThroughput,
232*6777b538SAndroid Build Coastguard Worker                        size_t(num_work_items / job_duration.InMilliseconds()));
233*6777b538SAndroid Build Coastguard Worker   }
234*6777b538SAndroid Build Coastguard Worker 
235*6777b538SAndroid Build Coastguard Worker   // Process |num_work_items| items with |process_item| in parallel. Work is
236*6777b538SAndroid Build Coastguard Worker   // assigned by having each worker sequentially traversing all items
237*6777b538SAndroid Build Coastguard Worker   // synchronized with an atomic variable.
RunJobWithAtomicAssignment(const std::string & story_name,size_t num_work_items,RepeatingCallback<void (size_t)> process_item,bool disruptive_post_tasks=false)238*6777b538SAndroid Build Coastguard Worker   void RunJobWithAtomicAssignment(const std::string& story_name,
239*6777b538SAndroid Build Coastguard Worker                                   size_t num_work_items,
240*6777b538SAndroid Build Coastguard Worker                                   RepeatingCallback<void(size_t)> process_item,
241*6777b538SAndroid Build Coastguard Worker                                   bool disruptive_post_tasks = false) {
242*6777b538SAndroid Build Coastguard Worker     WorkList work_list(num_work_items, std::move(process_item));
243*6777b538SAndroid Build Coastguard Worker     std::atomic_size_t index{0};
244*6777b538SAndroid Build Coastguard Worker 
245*6777b538SAndroid Build Coastguard Worker     // Post extra tasks to disrupt Job execution and cause workers to yield.
246*6777b538SAndroid Build Coastguard Worker     if (disruptive_post_tasks)
247*6777b538SAndroid Build Coastguard Worker       DisruptivePostTasks(10, Milliseconds(1));
248*6777b538SAndroid Build Coastguard Worker 
249*6777b538SAndroid Build Coastguard Worker     const TimeTicks job_run_start = TimeTicks::Now();
250*6777b538SAndroid Build Coastguard Worker 
251*6777b538SAndroid Build Coastguard Worker     WaitableEvent complete;
252*6777b538SAndroid Build Coastguard Worker     auto handle = PostJob(
253*6777b538SAndroid Build Coastguard Worker         FROM_HERE, {TaskPriority::USER_VISIBLE},
254*6777b538SAndroid Build Coastguard Worker         BindRepeating(
255*6777b538SAndroid Build Coastguard Worker             [](WorkList* work_list, WaitableEvent* complete,
256*6777b538SAndroid Build Coastguard Worker                std::atomic_size_t* index, JobDelegate* delegate) {
257*6777b538SAndroid Build Coastguard Worker               while (!delegate->ShouldYield()) {
258*6777b538SAndroid Build Coastguard Worker                 const size_t i = index->fetch_add(1, std::memory_order_relaxed);
259*6777b538SAndroid Build Coastguard Worker                 if (i >= work_list->NumWorkItems() ||
260*6777b538SAndroid Build Coastguard Worker                     !work_list->ProcessWorkItem(i)) {
261*6777b538SAndroid Build Coastguard Worker                   complete->Signal();
262*6777b538SAndroid Build Coastguard Worker                   return;
263*6777b538SAndroid Build Coastguard Worker                 }
264*6777b538SAndroid Build Coastguard Worker               }
265*6777b538SAndroid Build Coastguard Worker             },
266*6777b538SAndroid Build Coastguard Worker             Unretained(&work_list), Unretained(&complete), Unretained(&index)),
267*6777b538SAndroid Build Coastguard Worker         BindRepeating(&WorkList::NumIncompleteWorkItems,
268*6777b538SAndroid Build Coastguard Worker                       Unretained(&work_list)));
269*6777b538SAndroid Build Coastguard Worker 
270*6777b538SAndroid Build Coastguard Worker     complete.Wait();
271*6777b538SAndroid Build Coastguard Worker     handle.Join();
272*6777b538SAndroid Build Coastguard Worker     const TimeDelta job_duration = TimeTicks::Now() - job_run_start;
273*6777b538SAndroid Build Coastguard Worker     EXPECT_EQ(0U, work_list.NumIncompleteWorkItems(0));
274*6777b538SAndroid Build Coastguard Worker 
275*6777b538SAndroid Build Coastguard Worker     auto reporter = SetUpReporter(story_name);
276*6777b538SAndroid Build Coastguard Worker     reporter.AddResult(kMetricWorkThroughput,
277*6777b538SAndroid Build Coastguard Worker                        size_t(num_work_items / job_duration.InMilliseconds()));
278*6777b538SAndroid Build Coastguard Worker   }
279*6777b538SAndroid Build Coastguard Worker 
280*6777b538SAndroid Build Coastguard Worker   // Process |num_work_items| items with |process_item| in parallel. Work is
281*6777b538SAndroid Build Coastguard Worker   // assigned dynamically having each new worker given a different point far
282*6777b538SAndroid Build Coastguard Worker   // from other workers until all work is done. This is achieved by recursively
283*6777b538SAndroid Build Coastguard Worker   // splitting each range that was previously given in half.
RunJobWithDynamicAssignment(const std::string & story_name,size_t num_work_items,RepeatingCallback<void (size_t)> process_item,bool disruptive_post_tasks=false)284*6777b538SAndroid Build Coastguard Worker   void RunJobWithDynamicAssignment(const std::string& story_name,
285*6777b538SAndroid Build Coastguard Worker                                    size_t num_work_items,
286*6777b538SAndroid Build Coastguard Worker                                    RepeatingCallback<void(size_t)> process_item,
287*6777b538SAndroid Build Coastguard Worker                                    bool disruptive_post_tasks = false) {
288*6777b538SAndroid Build Coastguard Worker     WorkList work_list(num_work_items, std::move(process_item));
289*6777b538SAndroid Build Coastguard Worker     IndexGenerator generator(num_work_items);
290*6777b538SAndroid Build Coastguard Worker 
291*6777b538SAndroid Build Coastguard Worker     // Post extra tasks to disrupt Job execution and cause workers to yield.
292*6777b538SAndroid Build Coastguard Worker     if (disruptive_post_tasks)
293*6777b538SAndroid Build Coastguard Worker       DisruptivePostTasks(10, Milliseconds(1));
294*6777b538SAndroid Build Coastguard Worker 
295*6777b538SAndroid Build Coastguard Worker     const TimeTicks job_run_start = TimeTicks::Now();
296*6777b538SAndroid Build Coastguard Worker 
297*6777b538SAndroid Build Coastguard Worker     WaitableEvent complete;
298*6777b538SAndroid Build Coastguard Worker     auto handle = PostJob(
299*6777b538SAndroid Build Coastguard Worker         FROM_HERE, {TaskPriority::USER_VISIBLE},
300*6777b538SAndroid Build Coastguard Worker         BindRepeating(
301*6777b538SAndroid Build Coastguard Worker             [](IndexGenerator* generator, WorkList* work_list,
302*6777b538SAndroid Build Coastguard Worker                WaitableEvent* complete, JobDelegate* delegate) {
303*6777b538SAndroid Build Coastguard Worker               while (work_list->NumIncompleteWorkItems(0) != 0 &&
304*6777b538SAndroid Build Coastguard Worker                      !delegate->ShouldYield()) {
305*6777b538SAndroid Build Coastguard Worker                 std::optional<size_t> index = generator->GetNext();
306*6777b538SAndroid Build Coastguard Worker                 if (!index)
307*6777b538SAndroid Build Coastguard Worker                   return;
308*6777b538SAndroid Build Coastguard Worker                 for (size_t i = *index; i < work_list->NumWorkItems(); ++i) {
309*6777b538SAndroid Build Coastguard Worker                   if (delegate->ShouldYield()) {
310*6777b538SAndroid Build Coastguard Worker                     generator->GiveBack(i);
311*6777b538SAndroid Build Coastguard Worker                     return;
312*6777b538SAndroid Build Coastguard Worker                   }
313*6777b538SAndroid Build Coastguard Worker                   if (!work_list->TryAcquire(i)) {
314*6777b538SAndroid Build Coastguard Worker                     // If this was touched already, get a new starting point.
315*6777b538SAndroid Build Coastguard Worker                     break;
316*6777b538SAndroid Build Coastguard Worker                   }
317*6777b538SAndroid Build Coastguard Worker                   if (!work_list->ProcessWorkItem(i)) {
318*6777b538SAndroid Build Coastguard Worker                     complete->Signal();
319*6777b538SAndroid Build Coastguard Worker                     return;
320*6777b538SAndroid Build Coastguard Worker                   }
321*6777b538SAndroid Build Coastguard Worker                 }
322*6777b538SAndroid Build Coastguard Worker               }
323*6777b538SAndroid Build Coastguard Worker             },
324*6777b538SAndroid Build Coastguard Worker             Unretained(&generator), Unretained(&work_list),
325*6777b538SAndroid Build Coastguard Worker             Unretained(&complete)),
326*6777b538SAndroid Build Coastguard Worker         BindRepeating(&WorkList::NumIncompleteWorkItems,
327*6777b538SAndroid Build Coastguard Worker                       Unretained(&work_list)));
328*6777b538SAndroid Build Coastguard Worker 
329*6777b538SAndroid Build Coastguard Worker     complete.Wait();
330*6777b538SAndroid Build Coastguard Worker     handle.Join();
331*6777b538SAndroid Build Coastguard Worker     const TimeDelta job_duration = TimeTicks::Now() - job_run_start;
332*6777b538SAndroid Build Coastguard Worker     EXPECT_EQ(0U, work_list.NumIncompleteWorkItems(0));
333*6777b538SAndroid Build Coastguard Worker 
334*6777b538SAndroid Build Coastguard Worker     auto reporter = SetUpReporter(story_name);
335*6777b538SAndroid Build Coastguard Worker     reporter.AddResult(kMetricWorkThroughput,
336*6777b538SAndroid Build Coastguard Worker                        size_t(num_work_items / job_duration.InMilliseconds()));
337*6777b538SAndroid Build Coastguard Worker   }
338*6777b538SAndroid Build Coastguard Worker 
339*6777b538SAndroid Build Coastguard Worker   // Process |num_work_items| items with |process_item| in parallel. Work is
340*6777b538SAndroid Build Coastguard Worker   // assigned having each new worker given a different starting point far from
341*6777b538SAndroid Build Coastguard Worker   // other workers and loop over all work items from there. This is achieved by
342*6777b538SAndroid Build Coastguard Worker   // recursively splitting each range that was previously given in half.
RunJobWithLoopAround(const std::string & story_name,size_t num_work_items,RepeatingCallback<void (size_t)> process_item,bool disruptive_post_tasks=false)343*6777b538SAndroid Build Coastguard Worker   void RunJobWithLoopAround(const std::string& story_name,
344*6777b538SAndroid Build Coastguard Worker                             size_t num_work_items,
345*6777b538SAndroid Build Coastguard Worker                             RepeatingCallback<void(size_t)> process_item,
346*6777b538SAndroid Build Coastguard Worker                             bool disruptive_post_tasks = false) {
347*6777b538SAndroid Build Coastguard Worker     WorkList work_list(num_work_items, std::move(process_item));
348*6777b538SAndroid Build Coastguard Worker     IndexGenerator generator(num_work_items);
349*6777b538SAndroid Build Coastguard Worker 
350*6777b538SAndroid Build Coastguard Worker     // Post extra tasks to disrupt Job execution and cause workers to yield.
351*6777b538SAndroid Build Coastguard Worker     if (disruptive_post_tasks)
352*6777b538SAndroid Build Coastguard Worker       DisruptivePostTasks(10, Milliseconds(1));
353*6777b538SAndroid Build Coastguard Worker 
354*6777b538SAndroid Build Coastguard Worker     const TimeTicks job_run_start = TimeTicks::Now();
355*6777b538SAndroid Build Coastguard Worker 
356*6777b538SAndroid Build Coastguard Worker     WaitableEvent complete;
357*6777b538SAndroid Build Coastguard Worker     auto handle =
358*6777b538SAndroid Build Coastguard Worker         PostJob(FROM_HERE, {TaskPriority::USER_VISIBLE},
359*6777b538SAndroid Build Coastguard Worker                 BindRepeating(
360*6777b538SAndroid Build Coastguard Worker                     [](IndexGenerator* generator, WorkList* work_list,
361*6777b538SAndroid Build Coastguard Worker                        WaitableEvent* complete, JobDelegate* delegate) {
362*6777b538SAndroid Build Coastguard Worker                       std::optional<size_t> index = generator->GetNext();
363*6777b538SAndroid Build Coastguard Worker                       if (!index)
364*6777b538SAndroid Build Coastguard Worker                         return;
365*6777b538SAndroid Build Coastguard Worker                       size_t i = *index;
366*6777b538SAndroid Build Coastguard Worker                       while (true) {
367*6777b538SAndroid Build Coastguard Worker                         if (delegate->ShouldYield()) {
368*6777b538SAndroid Build Coastguard Worker                           generator->GiveBack(i);
369*6777b538SAndroid Build Coastguard Worker                           return;
370*6777b538SAndroid Build Coastguard Worker                         }
371*6777b538SAndroid Build Coastguard Worker                         if (!work_list->TryAcquire(i)) {
372*6777b538SAndroid Build Coastguard Worker                           // If this was touched already, skip.
373*6777b538SAndroid Build Coastguard Worker                           continue;
374*6777b538SAndroid Build Coastguard Worker                         }
375*6777b538SAndroid Build Coastguard Worker                         if (!work_list->ProcessWorkItem(i)) {
376*6777b538SAndroid Build Coastguard Worker                           // This will cause the loop to exit if there's no work
377*6777b538SAndroid Build Coastguard Worker                           // left.
378*6777b538SAndroid Build Coastguard Worker                           complete->Signal();
379*6777b538SAndroid Build Coastguard Worker                           return;
380*6777b538SAndroid Build Coastguard Worker                         }
381*6777b538SAndroid Build Coastguard Worker                         ++i;
382*6777b538SAndroid Build Coastguard Worker                         if (i == work_list->NumWorkItems())
383*6777b538SAndroid Build Coastguard Worker                           i = 0;
384*6777b538SAndroid Build Coastguard Worker                       }
385*6777b538SAndroid Build Coastguard Worker                     },
386*6777b538SAndroid Build Coastguard Worker                     Unretained(&generator), Unretained(&work_list),
387*6777b538SAndroid Build Coastguard Worker                     Unretained(&complete)),
388*6777b538SAndroid Build Coastguard Worker                 BindRepeating(&WorkList::NumIncompleteWorkItems,
389*6777b538SAndroid Build Coastguard Worker                               Unretained(&work_list)));
390*6777b538SAndroid Build Coastguard Worker 
391*6777b538SAndroid Build Coastguard Worker     complete.Wait();
392*6777b538SAndroid Build Coastguard Worker     handle.Join();
393*6777b538SAndroid Build Coastguard Worker     const TimeDelta job_duration = TimeTicks::Now() - job_run_start;
394*6777b538SAndroid Build Coastguard Worker     EXPECT_EQ(0U, work_list.NumIncompleteWorkItems(0));
395*6777b538SAndroid Build Coastguard Worker 
396*6777b538SAndroid Build Coastguard Worker     auto reporter = SetUpReporter(story_name);
397*6777b538SAndroid Build Coastguard Worker     reporter.AddResult(kMetricWorkThroughput,
398*6777b538SAndroid Build Coastguard Worker                        size_t(num_work_items / job_duration.InMilliseconds()));
399*6777b538SAndroid Build Coastguard Worker   }
400*6777b538SAndroid Build Coastguard Worker 
401*6777b538SAndroid Build Coastguard Worker  private:
402*6777b538SAndroid Build Coastguard Worker   test::TaskEnvironment task_environment;
403*6777b538SAndroid Build Coastguard Worker };
404*6777b538SAndroid Build Coastguard Worker 
405*6777b538SAndroid Build Coastguard Worker }  // namespace
406*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,NoOpWorkNaiveAssignment)407*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, NoOpWorkNaiveAssignment) {
408*6777b538SAndroid Build Coastguard Worker   RunJobWithNaiveAssignment(kStoryNoOpNaive, 10000000, DoNothing());
409*6777b538SAndroid Build Coastguard Worker }
410*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,BusyWaitNaiveAssignment)411*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, BusyWaitNaiveAssignment) {
412*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> callback = BusyWaitCallback(Microseconds(5));
413*6777b538SAndroid Build Coastguard Worker   RunJobWithNaiveAssignment(kStoryBusyWaitNaive, 500000, std::move(callback));
414*6777b538SAndroid Build Coastguard Worker }
415*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,NoOpWorkAtomicAssignment)416*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, NoOpWorkAtomicAssignment) {
417*6777b538SAndroid Build Coastguard Worker   RunJobWithAtomicAssignment(kStoryNoOpAtomic, 10000000, DoNothing());
418*6777b538SAndroid Build Coastguard Worker }
419*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,NoOpDisruptedWorkAtomicAssignment)420*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, NoOpDisruptedWorkAtomicAssignment) {
421*6777b538SAndroid Build Coastguard Worker   RunJobWithAtomicAssignment(kStoryNoOpAtomicDisrupted, 10000000, DoNothing(),
422*6777b538SAndroid Build Coastguard Worker                              true);
423*6777b538SAndroid Build Coastguard Worker }
424*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,BusyWaitAtomicAssignment)425*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, BusyWaitAtomicAssignment) {
426*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> callback = BusyWaitCallback(Microseconds(5));
427*6777b538SAndroid Build Coastguard Worker   RunJobWithAtomicAssignment(kStoryBusyWaitAtomic, 500000, std::move(callback));
428*6777b538SAndroid Build Coastguard Worker }
429*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,BusyWaitDisruptedWorkAtomicAssignment)430*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, BusyWaitDisruptedWorkAtomicAssignment) {
431*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> callback = BusyWaitCallback(Microseconds(5));
432*6777b538SAndroid Build Coastguard Worker   RunJobWithAtomicAssignment(kStoryBusyWaitAtomicDisrupted, 500000,
433*6777b538SAndroid Build Coastguard Worker                              std::move(callback), true);
434*6777b538SAndroid Build Coastguard Worker }
435*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,NoOpWorkDynamicAssignment)436*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, NoOpWorkDynamicAssignment) {
437*6777b538SAndroid Build Coastguard Worker   RunJobWithDynamicAssignment(kStoryNoOpDynamic, 10000000, DoNothing());
438*6777b538SAndroid Build Coastguard Worker }
439*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,NoOpDisruptedWorkDynamicAssignment)440*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, NoOpDisruptedWorkDynamicAssignment) {
441*6777b538SAndroid Build Coastguard Worker   RunJobWithDynamicAssignment(kStoryNoOpDynamicDisrupted, 10000000, DoNothing(),
442*6777b538SAndroid Build Coastguard Worker                               true);
443*6777b538SAndroid Build Coastguard Worker }
444*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,BusyWaitWorkDynamicAssignment)445*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, BusyWaitWorkDynamicAssignment) {
446*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> callback = BusyWaitCallback(Microseconds(5));
447*6777b538SAndroid Build Coastguard Worker   RunJobWithDynamicAssignment(kStoryBusyWaitDynamic, 500000,
448*6777b538SAndroid Build Coastguard Worker                               std::move(callback));
449*6777b538SAndroid Build Coastguard Worker }
450*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,BusyWaitDisruptedWorkDynamicAssignment)451*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, BusyWaitDisruptedWorkDynamicAssignment) {
452*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> callback = BusyWaitCallback(Microseconds(5));
453*6777b538SAndroid Build Coastguard Worker   RunJobWithDynamicAssignment(kStoryBusyWaitDynamicDisrupted, 500000,
454*6777b538SAndroid Build Coastguard Worker                               std::move(callback), true);
455*6777b538SAndroid Build Coastguard Worker }
456*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,NoOpWorkLoopAround)457*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, NoOpWorkLoopAround) {
458*6777b538SAndroid Build Coastguard Worker   RunJobWithLoopAround(kStoryNoOpLoopAround, 10000000, DoNothing());
459*6777b538SAndroid Build Coastguard Worker }
460*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,NoOpDisruptedWorkLoopAround)461*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, NoOpDisruptedWorkLoopAround) {
462*6777b538SAndroid Build Coastguard Worker   RunJobWithLoopAround(kStoryNoOpLoopAroundDisrupted, 10000000, DoNothing(),
463*6777b538SAndroid Build Coastguard Worker                        true);
464*6777b538SAndroid Build Coastguard Worker }
465*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,BusyWaitWorkLoopAround)466*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, BusyWaitWorkLoopAround) {
467*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> callback = BusyWaitCallback(Microseconds(5));
468*6777b538SAndroid Build Coastguard Worker   RunJobWithLoopAround(kStoryBusyWaitLoopAround, 500000, std::move(callback));
469*6777b538SAndroid Build Coastguard Worker }
470*6777b538SAndroid Build Coastguard Worker 
TEST_F(JobPerfTest,BusyWaitDisruptedWorkLoopAround)471*6777b538SAndroid Build Coastguard Worker TEST_F(JobPerfTest, BusyWaitDisruptedWorkLoopAround) {
472*6777b538SAndroid Build Coastguard Worker   RepeatingCallback<void(size_t)> callback = BusyWaitCallback(Microseconds(5));
473*6777b538SAndroid Build Coastguard Worker   RunJobWithLoopAround(kStoryBusyWaitLoopAroundDisrupted, 500000,
474*6777b538SAndroid Build Coastguard Worker                        std::move(callback), true);
475*6777b538SAndroid Build Coastguard Worker }
476*6777b538SAndroid Build Coastguard Worker 
477*6777b538SAndroid Build Coastguard Worker }  // namespace base
478