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/STLExtras.h"
22 #include "llvm/ADT/SmallVector.h"
23 #include "llvm/ADT/Statistic.h"
24 #include "llvm/Support/Format.h"
25 #include "llvm/Support/FormatVariadic.h"
26 #include "llvm/Support/Threading.h"
27 #include <chrono>
28 
29 using namespace mlir;
30 using namespace mlir::detail;
31 
32 constexpr llvm::StringLiteral kPassTimingDescription =
33     "... Pass execution timing report ...";
34 
35 namespace {
36 /// Simple record class to record timing information.
37 struct TimeRecord {
38   TimeRecord(double wall = 0.0, double user = 0.0) : wall(wall), user(user) {}
39 
40   TimeRecord &operator+=(const TimeRecord &other) {
41     wall += other.wall;
42     user += other.user;
43     return *this;
44   }
45 
46   /// Print the current time record to 'os', with a breakdown showing
47   /// contributions to the give 'total' time record.
48   void print(raw_ostream &os, const TimeRecord &total) {
49     if (total.user != total.wall)
50       os << llvm::format("  %7.4f (%5.1f%%)  ", user,
51                          100.0 * user / total.user);
52     os << llvm::format("  %7.4f (%5.1f%%)  ", wall, 100.0 * wall / total.wall);
53   }
54 
55   double wall, user;
56 };
57 
58 /// An enumeration of the different types of timers.
59 enum class TimerKind {
60   /// This timer represents an ordered collection of pass timers, corresponding
61   /// to a pass pipeline.
62   Pipeline,
63 
64   /// This timer represents a collection of pipeline timers.
65   PipelineCollection,
66 
67   /// This timer represents an analysis or pass timer.
68   PassOrAnalysis
69 };
70 
71 struct Timer {
72   explicit Timer(std::string &&name, TimerKind kind)
73       : name(std::move(name)), kind(kind) {}
74 
75   /// Start the timer.
76   void start() { startTime = std::chrono::system_clock::now(); }
77 
78   /// Stop the timer.
79   void stop() {
80     auto newTime = std::chrono::system_clock::now() - startTime;
81     wallTime += newTime;
82     userTime += newTime;
83   }
84 
85   /// Get or create a child timer with the provided name and id.
86   Timer *getChildTimer(const void *id, TimerKind kind,
87                        std::function<std::string()> &&nameBuilder) {
88     auto &child = children[id];
89     if (!child)
90       child = std::make_unique<Timer>(nameBuilder(), kind);
91     return child.get();
92   }
93 
94   /// Returns the total time for this timer in seconds.
95   TimeRecord getTotalTime() {
96     // If this is a pass or analysis timer, use the recorded time directly.
97     if (kind == TimerKind::PassOrAnalysis) {
98       return TimeRecord(
99           std::chrono::duration_cast<std::chrono::duration<double>>(wallTime)
100               .count(),
101           std::chrono::duration_cast<std::chrono::duration<double>>(userTime)
102               .count());
103     }
104 
105     // Otheriwse, accumulate the timing from each of the children.
106     TimeRecord totalTime;
107     for (auto &child : children)
108       totalTime += child.second->getTotalTime();
109     return totalTime;
110   }
111 
112   /// A map of unique identifiers to child timers.
113   using ChildrenMap = llvm::MapVector<const void *, std::unique_ptr<Timer>>;
114 
115   /// Merge the timing data from 'other' into this timer.
116   void merge(Timer &&other) {
117     if (wallTime < other.wallTime)
118       wallTime = other.wallTime;
119     userTime += other.userTime;
120     mergeChildren(std::move(other.children));
121   }
122 
123   /// Merge the timer chilren in 'otherChildren' with the children of this
124   /// timer.
125   void mergeChildren(ChildrenMap &&otherChildren) {
126     // Check for an empty children list.
127     if (children.empty()) {
128       children = std::move(otherChildren);
129       return;
130     }
131 
132     // Pipeline merges are handled separately as the children are merged
133     // lexographically.
134     if (kind == TimerKind::Pipeline) {
135       assert(children.size() == otherChildren.size() &&
136              "pipeline merge requires the same number of children");
137       for (auto it : llvm::zip(children, otherChildren))
138         std::get<0>(it).second->merge(std::move(*std::get<1>(it).second));
139       return;
140     }
141 
142     // Otherwise, we merge children based upon their timer key.
143     for (auto &otherChild : otherChildren)
144       mergeChild(std::move(otherChild));
145   }
146 
147   /// Merge in the given child timer and id into this timer.
148   void mergeChild(ChildrenMap::value_type &&childIt) {
149     auto &child = children[childIt.first];
150     if (!child)
151       child = std::move(childIt.second);
152     else
153       child->merge(std::move(*childIt.second));
154   }
155 
156   /// Raw timing information.
157   std::chrono::time_point<std::chrono::system_clock> startTime;
158   std::chrono::nanoseconds wallTime = std::chrono::nanoseconds(0);
159   std::chrono::nanoseconds userTime = std::chrono::nanoseconds(0);
160 
161   /// A map of unique identifiers to child timers.
162   ChildrenMap children;
163 
164   /// A descriptive name for this timer.
165   std::string name;
166 
167   /// The type of timer this instance represents.
168   TimerKind kind;
169 };
170 
171 struct PassTiming : public PassInstrumentation {
172   PassTiming(PassTimingDisplayMode displayMode) : displayMode(displayMode) {}
173   ~PassTiming() override { print(); }
174 
175   /// Setup the instrumentation hooks.
176   void runBeforePipeline(const OperationName &name,
177                          const PipelineParentInfo &parentInfo) override;
178   void runAfterPipeline(const OperationName &name,
179                         const PipelineParentInfo &parentInfo) override;
180   void runBeforePass(Pass *pass, Operation *) override { startPassTimer(pass); }
181   void runAfterPass(Pass *pass, Operation *) override;
182   void runAfterPassFailed(Pass *pass, Operation *op) override {
183     runAfterPass(pass, op);
184   }
185   void runBeforeAnalysis(llvm::StringRef name, AnalysisID *id,
186                          Operation *) override {
187     startAnalysisTimer(name, id);
188   }
189   void runAfterAnalysis(llvm::StringRef, AnalysisID *, Operation *) override;
190 
191   /// Print and clear the timing results.
192   void print();
193 
194   /// Start a new timer for the given pass.
195   void startPassTimer(Pass *pass);
196 
197   /// Start a new timer for the given analysis.
198   void startAnalysisTimer(llvm::StringRef name, AnalysisID *id);
199 
200   /// Pop the last active timer for the current thread.
201   Timer *popLastActiveTimer() {
202     auto tid = llvm::get_threadid();
203     auto &activeTimers = activeThreadTimers[tid];
204     assert(!activeTimers.empty() && "expected active timer");
205     return activeTimers.pop_back_val();
206   }
207 
208   /// Print the timing result in list mode.
209   void printResultsAsList(raw_ostream &os, Timer *root, TimeRecord totalTime);
210 
211   /// Print the timing result in pipeline mode.
212   void printResultsAsPipeline(raw_ostream &os, Timer *root,
213                               TimeRecord totalTime);
214 
215   /// Returns a timer for the provided identifier and name.
216   Timer *getTimer(const void *id, TimerKind kind,
217                   std::function<std::string()> &&nameBuilder) {
218     auto tid = llvm::get_threadid();
219 
220     // If there is no active timer then add to the root timer.
221     auto &activeTimers = activeThreadTimers[tid];
222     Timer *parentTimer;
223     if (activeTimers.empty()) {
224       auto &rootTimer = rootTimers[tid];
225       if (!rootTimer)
226         rootTimer = std::make_unique<Timer>("root", TimerKind::Pipeline);
227       parentTimer = rootTimer.get();
228     } else {
229       // Otherwise, add this to the active timer.
230       parentTimer = activeTimers.back();
231     }
232 
233     auto timer = parentTimer->getChildTimer(id, kind, std::move(nameBuilder));
234     activeTimers.push_back(timer);
235     return timer;
236   }
237 
238   /// The root top level timers for each thread.
239   DenseMap<uint64_t, std::unique_ptr<Timer>> rootTimers;
240 
241   /// A stack of the currently active pass timers per thread.
242   DenseMap<uint64_t, SmallVector<Timer *, 4>> activeThreadTimers;
243 
244   /// The display mode to use when printing the timing results.
245   PassTimingDisplayMode displayMode;
246 
247   /// A mapping of pipeline timers that need to be merged into the parent
248   /// collection. The timers are mapped to the parent info to merge into.
249   DenseMap<PipelineParentInfo, SmallVector<Timer::ChildrenMap::value_type, 4>>
250       pipelinesToMerge;
251 };
252 } // end anonymous namespace
253 
254 void PassTiming::runBeforePipeline(const OperationName &name,
255                                    const PipelineParentInfo &parentInfo) {
256   // We don't actually want to time the piplelines, they gather their total
257   // from their held passes.
258   getTimer(name.getAsOpaquePointer(), TimerKind::Pipeline,
259            [&] { return ("'" + name.getStringRef() + "' Pipeline").str(); });
260 }
261 
262 void PassTiming::runAfterPipeline(const OperationName &name,
263                                   const PipelineParentInfo &parentInfo) {
264   // Pop the timer for the pipeline.
265   auto tid = llvm::get_threadid();
266   auto &activeTimers = activeThreadTimers[tid];
267   assert(!activeTimers.empty() && "expected active timer");
268   activeTimers.pop_back();
269 
270   // If the current thread is the same as the parent, there is nothing left to
271   // do.
272   if (tid == parentInfo.parentThreadID)
273     return;
274 
275   // Otherwise, mark the pipeline timer for merging into the correct parent
276   // thread.
277   assert(activeTimers.empty() && "expected parent timer to be root");
278   auto *parentTimer = rootTimers[tid].get();
279   assert(parentTimer->children.size() == 1 &&
280          parentTimer->children.count(name.getAsOpaquePointer()) &&
281          "expected a single pipeline timer");
282   pipelinesToMerge[parentInfo].push_back(
283       std::move(*parentTimer->children.begin()));
284   rootTimers.erase(tid);
285 }
286 
287 /// Start a new timer for the given pass.
288 void PassTiming::startPassTimer(Pass *pass) {
289   auto kind = isAdaptorPass(pass) ? TimerKind::PipelineCollection
290                                   : TimerKind::PassOrAnalysis;
291   Timer *timer = getTimer(pass, kind, [pass]() -> std::string {
292     if (auto *adaptor = getAdaptorPassBase(pass)) {
293       std::string name = "Pipeline Collection : [";
294       llvm::raw_string_ostream os(name);
295       interleaveComma(adaptor->getPassManagers(), os, [&](OpPassManager &pm) {
296         os << '\'' << pm.getOpName() << '\'';
297       });
298       os << ']';
299       return os.str();
300     }
301     return pass->getName();
302   });
303 
304   // We don't actually want to time the adaptor passes, they gather their total
305   // from their held passes.
306   if (!isAdaptorPass(pass))
307     timer->start();
308 }
309 
310 /// Start a new timer for the given analysis.
311 void PassTiming::startAnalysisTimer(llvm::StringRef name, AnalysisID *id) {
312   Timer *timer = getTimer(id, TimerKind::PassOrAnalysis,
313                           [name] { return "(A) " + name.str(); });
314   timer->start();
315 }
316 
317 /// Stop a pass timer.
318 void PassTiming::runAfterPass(Pass *pass, Operation *) {
319   Timer *timer = popLastActiveTimer();
320 
321   // If this is an OpToOpPassAdaptorParallel, then we need to merge in the
322   // timing data for the pipelines running on other threads.
323   if (isa<OpToOpPassAdaptorParallel>(pass)) {
324     auto toMerge = pipelinesToMerge.find({llvm::get_threadid(), pass});
325     if (toMerge != pipelinesToMerge.end()) {
326       for (auto &it : toMerge->second)
327         timer->mergeChild(std::move(it));
328       pipelinesToMerge.erase(toMerge);
329     }
330     return;
331   }
332 
333   // Adapator passes aren't timed directly, so we don't need to stop their
334   // timers.
335   if (!isAdaptorPass(pass))
336     timer->stop();
337 }
338 
339 /// Stop a timer.
340 void PassTiming::runAfterAnalysis(llvm::StringRef, AnalysisID *, Operation *) {
341   popLastActiveTimer()->stop();
342 }
343 
344 /// Utility to print the timer heading information.
345 static void printTimerHeader(llvm::raw_ostream &os, TimeRecord total) {
346   os << "===" << std::string(73, '-') << "===\n";
347   // Figure out how many spaces to description name.
348   unsigned Padding = (80 - kPassTimingDescription.size()) / 2;
349   os.indent(Padding) << kPassTimingDescription << '\n';
350   os << "===" << std::string(73, '-') << "===\n";
351 
352   // Print the total time followed by the section headers.
353   os << llvm::format("  Total Execution Time: %5.4f seconds\n\n", total.wall);
354   if (total.user != total.wall)
355     os << "   ---User Time---";
356   os << "   ---Wall Time---  --- Name ---\n";
357 }
358 
359 /// Utility to print a single line entry in the timer output.
360 static void printTimeEntry(raw_ostream &os, unsigned indent, StringRef name,
361                            TimeRecord time, TimeRecord totalTime) {
362   time.print(os, totalTime);
363   os.indent(indent) << name << "\n";
364 }
365 
366 /// Print out the current timing information.
367 void PassTiming::print() {
368   // Don't print anything if there is no timing data.
369   if (rootTimers.empty())
370     return;
371 
372   assert(rootTimers.size() == 1 && "expected one remaining root timer");
373   auto &rootTimer = rootTimers.begin()->second;
374   auto os = llvm::CreateInfoOutputFile();
375 
376   // Print the timer header.
377   TimeRecord totalTime = rootTimer->getTotalTime();
378   printTimerHeader(*os, totalTime);
379 
380   // Defer to a specialized printer for each display mode.
381   switch (displayMode) {
382   case PassTimingDisplayMode::List:
383     printResultsAsList(*os, rootTimer.get(), totalTime);
384     break;
385   case PassTimingDisplayMode::Pipeline:
386     printResultsAsPipeline(*os, rootTimer.get(), totalTime);
387     break;
388   }
389   printTimeEntry(*os, 0, "Total", totalTime, totalTime);
390   os->flush();
391 
392   // Reset root timers.
393   rootTimers.clear();
394   activeThreadTimers.clear();
395 }
396 
397 /// Print the timing result in list mode.
398 void PassTiming::printResultsAsList(raw_ostream &os, Timer *root,
399                                     TimeRecord totalTime) {
400   llvm::StringMap<TimeRecord> mergedTimings;
401 
402   std::function<void(Timer *)> addTimer = [&](Timer *timer) {
403     // Only add timing information for passes and analyses.
404     if (timer->kind == TimerKind::PassOrAnalysis)
405       mergedTimings[timer->name] += timer->getTotalTime();
406     for (auto &children : timer->children)
407       addTimer(children.second.get());
408   };
409 
410   // Add each of the top level timers.
411   for (auto &topLevelTimer : root->children)
412     addTimer(topLevelTimer.second.get());
413 
414   // Sort the timing information by wall time.
415   std::vector<std::pair<StringRef, TimeRecord>> timerNameAndTime;
416   for (auto &it : mergedTimings)
417     timerNameAndTime.emplace_back(it.first(), it.second);
418   llvm::array_pod_sort(timerNameAndTime.begin(), timerNameAndTime.end(),
419                        [](const std::pair<StringRef, TimeRecord> *lhs,
420                           const std::pair<StringRef, TimeRecord> *rhs) {
421                          return llvm::array_pod_sort_comparator<double>(
422                              &rhs->second.wall, &lhs->second.wall);
423                        });
424 
425   // Print the timing information sequentially.
426   for (auto &timeData : timerNameAndTime)
427     printTimeEntry(os, 0, timeData.first, timeData.second, totalTime);
428 }
429 
430 /// Print the timing result in pipeline mode.
431 void PassTiming::printResultsAsPipeline(raw_ostream &os, Timer *root,
432                                         TimeRecord totalTime) {
433   std::function<void(unsigned, Timer *)> printTimer = [&](unsigned indent,
434                                                           Timer *timer) {
435     // If this is a timer for a pipeline collection and the collection only has
436     // one pipeline child, then only print the child.
437     if (timer->kind == TimerKind::PipelineCollection &&
438         timer->children.size() == 1)
439       return printTimer(indent, timer->children.begin()->second.get());
440 
441     printTimeEntry(os, indent, timer->name, timer->getTotalTime(), totalTime);
442 
443     // If this timer is a pipeline, then print the children in-order.
444     if (timer->kind == TimerKind::Pipeline) {
445       for (auto &child : timer->children)
446         printTimer(indent + 2, child.second.get());
447       return;
448     }
449 
450     // Otherwise, sort the children by name to give a deterministic ordering
451     // when emitting the time.
452     SmallVector<Timer *, 4> children;
453     children.reserve(timer->children.size());
454     for (auto &child : timer->children)
455       children.push_back(child.second.get());
456     llvm::array_pod_sort(children.begin(), children.end(),
457                          [](Timer *const *lhs, Timer *const *rhs) {
458                            return (*lhs)->name.compare((*rhs)->name);
459                          });
460     for (auto &child : children)
461       printTimer(indent + 2, child);
462   };
463 
464   // Print each of the top level timers.
465   for (auto &topLevelTimer : root->children)
466     printTimer(0, topLevelTimer.second.get());
467 }
468 
469 //===----------------------------------------------------------------------===//
470 // PassManager
471 //===----------------------------------------------------------------------===//
472 
473 /// Add an instrumentation to time the execution of passes and the computation
474 /// of analyses.
475 void PassManager::enableTiming(PassTimingDisplayMode displayMode) {
476   // Check if pass timing is already enabled.
477   if (passTiming)
478     return;
479   addInstrumentation(std::make_unique<PassTiming>(displayMode));
480   passTiming = true;
481 }
482