1 //===- PassTiming.cpp -----------------------------------------------------===//
2 //
3 // Copyright 2019 The MLIR Authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //   http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 // =============================================================================
17 
18 #include "PassDetail.h"
19 #include "mlir/Pass/PassManager.h"
20 #include "llvm/ADT/MapVector.h"
21 #include "llvm/ADT/SmallVector.h"
22 #include "llvm/ADT/Statistic.h"
23 #include "llvm/Support/Format.h"
24 #include "llvm/Support/FormatVariadic.h"
25 #include "llvm/Support/Threading.h"
26 #include <chrono>
27 
28 using namespace mlir;
29 using namespace mlir::detail;
30 
31 constexpr llvm::StringLiteral kPassTimingDescription =
32     "... Pass execution timing report ...";
33 
34 namespace {
35 /// Simple record class to record timing information.
36 struct TimeRecord {
37   TimeRecord(double wall = 0.0, double user = 0.0) : wall(wall), user(user) {}
38 
39   TimeRecord &operator+=(const TimeRecord &other) {
40     wall += other.wall;
41     user += other.user;
42     return *this;
43   }
44 
45   /// Print the current time record to 'os', with a breakdown showing
46   /// contributions to the give 'total' time record.
47   void print(raw_ostream &os, const TimeRecord &total) {
48     if (total.user != total.wall)
49       os << llvm::format("  %7.4f (%5.1f%%)  ", user,
50                          100.0 * user / total.user);
51     os << llvm::format("  %7.4f (%5.1f%%)  ", wall, 100.0 * wall / total.wall);
52   }
53 
54   double wall, user;
55 };
56 
57 struct Timer {
58   explicit Timer(std::string &&name) : name(std::move(name)) {}
59 
60   /// Start the timer.
61   void start() { startTime = std::chrono::system_clock::now(); }
62 
63   /// Stop the timer.
64   void stop() {
65     auto newTime = std::chrono::system_clock::now() - startTime;
66     wallTime += newTime;
67     userTime += newTime;
68   }
69 
70   /// Get or create a child timer with the provided name and id.
71   Timer *getChildTimer(const void *id,
72                        std::function<std::string()> &&nameBuilder) {
73     auto &child = children[id];
74     if (!child)
75       child.reset(new Timer(nameBuilder()));
76     return child.get();
77   }
78 
79   /// Returns the total time for this timer in seconds.
80   TimeRecord getTotalTime() {
81     // If we have a valid wall time, then we directly compute the seconds.
82     if (wallTime.count()) {
83       return TimeRecord(
84           std::chrono::duration_cast<std::chrono::duration<double>>(wallTime)
85               .count(),
86           std::chrono::duration_cast<std::chrono::duration<double>>(userTime)
87               .count());
88     }
89 
90     // Otheriwse, accumulate the timing from each of the children.
91     TimeRecord totalTime;
92     for (auto &child : children)
93       totalTime += child.second->getTotalTime();
94     return totalTime;
95   }
96 
97   /// A map of unique identifiers to child timers.
98   using ChildrenMap = llvm::MapVector<const void *, std::unique_ptr<Timer>>;
99 
100   /// Merge the timing data from 'other' into this timer.
101   void merge(Timer &&other) {
102     if (wallTime < other.wallTime)
103       wallTime = other.wallTime;
104     userTime += other.userTime;
105     mergeChildren(std::move(other.children), /*isStructural=*/false);
106   }
107 
108   /// Merge the timer chilren in 'otherChildren' with the children of this
109   /// timer. If 'isStructural' is true, the children are merged lexographically
110   /// and 'otherChildren' must have the same number of elements as the children
111   /// of this timer. Otherwise, the timer children are merged based upon the
112   /// given timer key.
113   void mergeChildren(ChildrenMap &&otherChildren, bool isStructural) {
114     // Check for an empty children list.
115     if (children.empty()) {
116       children = std::move(otherChildren);
117       return;
118     }
119 
120     if (isStructural) {
121       // If this is a structural merge, the number of children must be the same.
122       assert(children.size() == otherChildren.size() &&
123              "structural merge requires the same number of children");
124       auto it = children.begin(), otherIt = otherChildren.begin();
125       for (auto e = children.end(); it != e; ++it, ++otherIt)
126         it->second->merge(std::move(*otherIt->second));
127       return;
128     }
129 
130     // Otherwise, we merge based upon the child timers key.
131     for (auto &otherChild : otherChildren) {
132       auto &child = children[otherChild.first];
133       if (!child)
134         child = std::move(otherChild.second);
135       else
136         child->merge(std::move(*otherChild.second));
137     }
138   }
139 
140   /// Raw timing information.
141   std::chrono::time_point<std::chrono::system_clock> startTime;
142   std::chrono::nanoseconds wallTime = std::chrono::nanoseconds(0);
143   std::chrono::nanoseconds userTime = std::chrono::nanoseconds(0);
144 
145   /// A map of unique identifiers to child timers.
146   ChildrenMap children;
147 
148   /// A descriptive name for this timer.
149   std::string name;
150 };
151 
152 struct PassTiming : public PassInstrumentation {
153   PassTiming(PassTimingDisplayMode displayMode) : displayMode(displayMode) {}
154   ~PassTiming() { print(); }
155 
156   /// Setup the instrumentation hooks.
157   void runBeforePass(Pass *pass, Operation *) override { startPassTimer(pass); }
158   void runAfterPass(Pass *pass, Operation *) override;
159   void runAfterPassFailed(Pass *pass, Operation *op) override {
160     runAfterPass(pass, op);
161   }
162   void runBeforeAnalysis(llvm::StringRef name, AnalysisID *id,
163                          Operation *) override {
164     startAnalysisTimer(name, id);
165   }
166   void runAfterAnalysis(llvm::StringRef, AnalysisID *, Operation *) override;
167 
168   /// Print and clear the timing results.
169   void print();
170 
171   /// Start a new timer for the given pass.
172   void startPassTimer(Pass *pass);
173 
174   /// Start a new timer for the given analysis.
175   void startAnalysisTimer(llvm::StringRef name, AnalysisID *id);
176 
177   /// Stop a pass timer.
178   void stopPassTimer(Pass *pass);
179 
180   /// Stop the last active timer.
181   void stopTimer();
182 
183   /// Print the timing result in list mode.
184   void printResultsAsList(raw_ostream &os, Timer *root, TimeRecord totalTime);
185 
186   /// Print the timing result in pipeline mode.
187   void printResultsAsPipeline(raw_ostream &os, Timer *root,
188                               TimeRecord totalTime);
189 
190   /// Returns a timer for the provided identifier and name.
191   Timer *getTimer(const void *id, std::function<std::string()> &&nameBuilder) {
192     auto tid = llvm::get_threadid();
193 
194     // If there is no active timer then add to the root timer.
195     auto &activeTimers = activeThreadTimers[tid];
196     if (activeTimers.empty()) {
197       auto &rootTimer = rootTimers[tid];
198       if (!rootTimer)
199         rootTimer.reset(new Timer("root"));
200       auto *timer = rootTimer->getChildTimer(id, std::move(nameBuilder));
201       activeTimers.push_back(timer);
202       return timer;
203     }
204 
205     // Otherwise, add this to the active timer.
206     auto timer = activeTimers.back()->getChildTimer(id, std::move(nameBuilder));
207     activeTimers.push_back(timer);
208     return timer;
209   }
210 
211   /// The root top level timers for each thread.
212   DenseMap<uint64_t, std::unique_ptr<Timer>> rootTimers;
213 
214   /// A stack of the currently active pass timers per thread.
215   DenseMap<uint64_t, SmallVector<Timer *, 4>> activeThreadTimers;
216 
217   /// The display mode to use when printing the timing results.
218   PassTimingDisplayMode displayMode;
219 };
220 } // end anonymous namespace
221 
222 /// Start a new timer for the given pass.
223 void PassTiming::startPassTimer(Pass *pass) {
224   Timer *timer = getTimer(pass, [pass] {
225     if (isModuleToFunctionAdaptorPass(pass))
226       return StringRef("Function Pipeline");
227     return pass->getName();
228   });
229 
230   // We don't actually want to time the adaptor passes, they gather their total
231   // from their held passes.
232   if (!isAdaptorPass(pass))
233     timer->start();
234 }
235 
236 /// Start a new timer for the given analysis.
237 void PassTiming::startAnalysisTimer(llvm::StringRef name, AnalysisID *id) {
238   Timer *timer = getTimer(id, [name] { return "(A) " + name.str(); });
239   timer->start();
240 }
241 
242 /// Stop a pass timer.
243 void PassTiming::runAfterPass(Pass *pass, Operation *) {
244   auto tid = llvm::get_threadid();
245   auto &activeTimers = activeThreadTimers[tid];
246   assert(!activeTimers.empty() && "expected active timer");
247   Timer *timer = activeTimers.pop_back_val();
248 
249   // If this is an ModuleToFunctionPassAdaptorParallel, then we need to merge in
250   // the timing data for the other threads.
251   if (isa<ModuleToFunctionPassAdaptorParallel>(pass)) {
252     // The asychronous pipeline timers should exist as children of root timers
253     // for other threads.
254     for (auto &rootTimer : llvm::make_early_inc_range(rootTimers)) {
255       // Skip the current thread.
256       if (rootTimer.first == tid)
257         continue;
258       // Check that this thread has no active timers.
259       assert(activeThreadTimers[tid].empty() && "expected no active timers");
260 
261       // Structurally merge this timers children into the parallel
262       // module-to-function pass timer.
263       timer->mergeChildren(std::move(rootTimer.second->children),
264                            /*isStructural=*/true);
265       rootTimers.erase(rootTimer.first);
266     }
267     return;
268   }
269 
270   // Adapator passes aren't timed directly, so we don't need to stop their
271   // timers.
272   if (!isAdaptorPass(pass))
273     timer->stop();
274 }
275 
276 /// Stop a timer.
277 void PassTiming::runAfterAnalysis(llvm::StringRef, AnalysisID *, Operation *) {
278   auto &activeTimers = activeThreadTimers[llvm::get_threadid()];
279   assert(!activeTimers.empty() && "expected active timer");
280   Timer *timer = activeTimers.pop_back_val();
281   timer->stop();
282 }
283 
284 /// Utility to print the timer heading information.
285 static void printTimerHeader(llvm::raw_ostream &os, TimeRecord total) {
286   os << "===" << std::string(73, '-') << "===\n";
287   // Figure out how many spaces to description name.
288   unsigned Padding = (80 - kPassTimingDescription.size()) / 2;
289   os.indent(Padding) << kPassTimingDescription << '\n';
290   os << "===" << std::string(73, '-') << "===\n";
291 
292   // Print the total time followed by the section headers.
293   os << llvm::format("  Total Execution Time: %5.4f seconds\n\n", total.wall);
294   if (total.user != total.wall)
295     os << "   ---User Time---";
296   os << "   ---Wall Time---  --- Name ---\n";
297 }
298 
299 /// Utility to print a single line entry in the timer output.
300 static void printTimeEntry(raw_ostream &os, unsigned indent, StringRef name,
301                            TimeRecord time, TimeRecord totalTime) {
302   time.print(os, totalTime);
303   os.indent(indent) << name << "\n";
304 }
305 
306 /// Print out the current timing information.
307 void PassTiming::print() {
308   // Don't print anything if there is no timing data.
309   if (rootTimers.empty())
310     return;
311 
312   assert(rootTimers.size() == 1 && "expected one remaining root timer");
313   auto &rootTimer = rootTimers.begin()->second;
314   auto os = llvm::CreateInfoOutputFile();
315 
316   // Print the timer header.
317   TimeRecord totalTime = rootTimer->getTotalTime();
318   printTimerHeader(*os, totalTime);
319 
320   // Defer to a specialized printer for each display mode.
321   switch (displayMode) {
322   case PassTimingDisplayMode::List:
323     printResultsAsList(*os, rootTimer.get(), totalTime);
324     break;
325   case PassTimingDisplayMode::Pipeline:
326     printResultsAsPipeline(*os, rootTimer.get(), totalTime);
327     break;
328   }
329   printTimeEntry(*os, 0, "Total", totalTime, totalTime);
330   os->flush();
331 
332   // Reset root timers.
333   rootTimers.clear();
334   activeThreadTimers.clear();
335 }
336 
337 /// Print the timing result in list mode.
338 void PassTiming::printResultsAsList(raw_ostream &os, Timer *root,
339                                     TimeRecord totalTime) {
340   llvm::StringMap<TimeRecord> mergedTimings;
341 
342   std::function<void(Timer *)> addTimer = [&](Timer *timer) {
343     // Check for timing information.
344     if (timer->wallTime.count())
345       mergedTimings[timer->name] += timer->getTotalTime();
346     for (auto &children : timer->children)
347       addTimer(children.second.get());
348   };
349 
350   // Add each of the top level timers.
351   for (auto &topLevelTimer : root->children)
352     addTimer(topLevelTimer.second.get());
353 
354   // Sort the timing information by wall time.
355   std::vector<std::pair<StringRef, TimeRecord>> timerNameAndTime;
356   for (auto &it : mergedTimings)
357     timerNameAndTime.emplace_back(it.first(), it.second);
358   llvm::array_pod_sort(timerNameAndTime.begin(), timerNameAndTime.end(),
359                        [](const std::pair<StringRef, TimeRecord> *lhs,
360                           const std::pair<StringRef, TimeRecord> *rhs) {
361                          return llvm::array_pod_sort_comparator<double>(
362                              &rhs->second.wall, &lhs->second.wall);
363                        });
364 
365   // Print the timing information sequentially.
366   for (auto &timeData : timerNameAndTime)
367     printTimeEntry(os, 0, timeData.first, timeData.second, totalTime);
368 }
369 
370 /// Print the timing result in pipeline mode.
371 void PassTiming::printResultsAsPipeline(raw_ostream &os, Timer *root,
372                                         TimeRecord totalTime) {
373   std::function<void(unsigned, Timer *)> printTimer = [&](unsigned indent,
374                                                           Timer *timer) {
375     printTimeEntry(os, indent, timer->name, timer->getTotalTime(), totalTime);
376     for (auto &children : timer->children)
377       printTimer(indent + 2, children.second.get());
378   };
379 
380   // Print each of the top level timers.
381   for (auto &topLevelTimer : root->children)
382     printTimer(0, topLevelTimer.second.get());
383 }
384 
385 //===----------------------------------------------------------------------===//
386 // PassManager
387 //===----------------------------------------------------------------------===//
388 
389 /// Add an instrumentation to time the execution of passes and the computation
390 /// of analyses.
391 void PassManager::enableTiming(PassTimingDisplayMode displayMode) {
392   // Check if pass timing is already enabled.
393   if (passTiming)
394     return;
395   addInstrumentation(new PassTiming(displayMode));
396   passTiming = true;
397 }
398