[Profiler] Separate Profiler for wider use
authorJihoon Lee <jhoon.it.lee@samsung.com>
Wed, 9 Dec 2020 04:09:34 +0000 (13:09 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Tue, 15 Dec 2020 04:57:32 +0000 (13:57 +0900)
This patch extracts profiler from neuralnet.

Also, this seperates `ProfileListener` which
should be used for client side while `Profiler`
is used in library side

**Self evaluation:**
1. Build test: [X]Passed [ ]Failed [ ]Skipped
2. Run test: [X]Passed [ ]Failed [ ]Skipped

Signed-off-by: Jihoon Lee <jhoon.it.lee@samsung.com>
nntrainer/models/neuralnet.cpp
nntrainer/models/neuralnet.h
nntrainer/utils/profiler.cpp [new file with mode: 0644]
nntrainer/utils/profiler.h [new file with mode: 0644]

index 9700ace..8a4505e 100644 (file)
@@ -413,13 +413,7 @@ sharedConstTensors NeuralNetwork::inference(sharedConstTensors X) {
 
   sharedConstTensors out;
   try {
-#ifdef PROFILE
-    profiler.start(Profiler::FORWARD);
-#endif
     forwarding(X);
-#ifdef PROFILE
-    profiler.end(Profiler::FORWARD);
-#endif
     /** Forward loss layer without label as well */
     std::static_pointer_cast<LossLayer>(model_graph.Sorted.back().layer)
       ->forwarding();
index c4ca37f..b78797f 100644 (file)
@@ -356,71 +356,6 @@ public:
     manager.setGradientMemoryOptimization(opt);
   }
 
-/// @todo Make a more common class have this
-/// Maybe appcontext can have this?
-#ifdef PROFILE
-  class Profiler {
-  public:
-    // Designated key.
-    enum { FORWARD = 0 };
-
-    /**
-     * @brief query profile result
-     *
-     * @return std::chrono::time_point<std::chrono::steady_clock> profile result
-     */
-    std::chrono::milliseconds result(const int &key) { return time_taken[key]; }
-
-    /**
-     * @brief start profile
-     *
-     * @param key to record the profile result. Either designated key from enum
-     * or arbitrary key can be used
-     */
-    void start(const int &key) {
-      /// @todo: check if key is being reused with time_taken
-      start_time[key] = std::chrono::steady_clock::now();
-    }
-
-    /**
-     * @brief end profile
-     *
-     * @param key to record the profile result. Either designated key from enum
-     * or arbitrary key can be used
-     */
-    void end(const int &key) {
-      auto end = std::chrono::steady_clock::now();
-      auto iter = start_time.find(key);
-
-      if (iter == start_time.end()) {
-        throw std::invalid_argument("profiler hasn't started with the key");
-      }
-
-      time_taken[key] = std::chrono::duration_cast<std::chrono::milliseconds>(
-        end - iter->second);
-    }
-
-  private:
-    std::unordered_map<int, std::chrono::time_point<std::chrono::steady_clock>>
-      start_time; /**< start_time of the clock */
-    std::unordered_map<int, std::chrono::milliseconds> time_taken;
-  };
-
-public:
-  /**
-   * @brief Get the Profile Result
-   *
-   * @param key key to recorder the profile
-   * @return std::chrono::time_point<std::chrono::steady_clock>
-   */
-  std::chrono::milliseconds getProfileResult(const int &key) {
-    return profiler.result(key);
-  }
-
-private:
-  Profiler profiler;
-#endif
-
 private:
   /**
    * @brief   Print Options when printing layer info
diff --git a/nntrainer/utils/profiler.cpp b/nntrainer/utils/profiler.cpp
new file mode 100644 (file)
index 0000000..327635b
--- /dev/null
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: Apache-2.0
+/**
+ * Copyright (C) 2020 Jihoon Lee <jhoon.it.lee@samsung.com>
+ *
+ * @file   profiler.cpp
+ * @date   09 December 2020
+ * @brief  Profiler related codes to be used to benchmark things
+ * @see    https://github.com/nnstreamer/nntrainer
+ * @author Jihoon Lee <jhoon.it.lee@samsung.com>
+ * @bug    No known bugs except for NYI items
+ *
+ */
+#include <sstream>
+
+#include <profiler.h>
+
+namespace nntrainer {
+namespace profile {
+
+Profiler &Profiler::Global() {
+  static Profiler instance;
+  return instance;
+}
+
+std::string event_to_str(const EVENT event) {
+  switch (event) {
+  case EVENT::NN_FORWARD:
+    return "nn_forward";
+  case EVENT::TEMP:
+    return "temp";
+  }
+
+  std::stringstream ss;
+  ss << "undefined key - " << event;
+  return ss.str();
+}
+
+void Profiler::start(const EVENT &event) {
+  /// @todo: consider race condition
+  auto iter = start_time.find(event);
+
+  if (iter != start_time.end()) {
+    throw std::invalid_argument("profiler has already started");
+  }
+
+  start_time[event] = std::chrono::steady_clock::now();
+}
+
+void Profiler::end(const EVENT &event) {
+  /// @todo: consider race condition
+  auto end = std::chrono::steady_clock::now();
+  auto iter = start_time.find(event);
+
+  if (iter == start_time.end()) {
+    throw std::invalid_argument("profiler hasn't started with the event");
+  }
+
+  auto duration =
+    std::chrono::duration_cast<std::chrono::milliseconds>(end - iter->second);
+  notify(event, duration);
+
+  start_time.erase(iter);
+}
+
+void Profiler::notify(const EVENT &event,
+                      const std::chrono::milliseconds &value) {
+  for (auto listener : all_event_listeners) {
+    listener->onNotify(event, value);
+  }
+  auto items = event_listeners[event];
+
+  for (auto listner : items) {
+    listner->onNotify(event, value);
+  }
+}
+
+void Profiler::subscribe(ProfileListener *listener,
+                         const std::vector<EVENT> &events) {
+
+  if (listener == nullptr) {
+    throw std::invalid_argument("listener is null!");
+  }
+
+  if (events.empty()) {
+    all_event_listeners.push_back(listener);
+    return;
+  }
+
+  for (auto event : events) {
+    auto iter = event_listeners.find(event);
+    if (iter == event_listeners.end()) {
+      event_listeners[event] = std::vector<ProfileListener *>{};
+    }
+    event_listeners[event].push_back(listener);
+  }
+}
+
+} // namespace profile
+
+} // namespace nntrainer
diff --git a/nntrainer/utils/profiler.h b/nntrainer/utils/profiler.h
new file mode 100644 (file)
index 0000000..f563f8a
--- /dev/null
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: Apache-2.0
+/**
+ * Copyright (C) 2020 Jihoon Lee <jhoon.it.lee@samsung.com>
+ *
+ * @file   profiler.h
+ * @date   09 December 2020
+ * @brief  Profiler related codes to be used to benchmark things
+ * @see    https://github.com/nnstreamer/nntrainer
+ * @author Jihoon Lee <jhoon.it.lee@samsung.com>
+ * @bug    No known bugs except for NYI items
+ *
+ */
+
+#ifndef __PROFILER_H__
+#define __PROFILER_H__
+
+#ifndef PROFILE
+
+#define PROFILE_START(event_key)
+#define PROFILE_END(event_key)
+
+#else
+
+#define PROFILE_START(event_key)                         \
+  do {                                                   \
+    auto &prof = nntrainer::profile::Profiler::Global(); \
+    prof.start(event_key);                               \
+  } while (0);
+
+#define PROFILE_END(event_key)                           \
+  do {                                                   \
+    auto &prof = nntrainer::profile::Profiler::Global(); \
+    prof.end(event_key);                                 \
+  } while (0);
+
+#endif /** PROFILE */
+
+#include <chrono>
+#include <iostream>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace nntrainer {
+namespace profile {
+
+typedef enum {
+  NN_FORWARD = 0 /**< Neuralnet single inference without loss calculation */,
+  TEMP = 999 /**< Temporary event */
+} EVENT;
+
+/**
+ * @brief get string representation of event
+ *
+ * @return std::string name
+ */
+std::string event_to_str(const EVENT event);
+
+/**
+ * @brief Generic profile listener class to attach to a profiler,
+ * this can be inherited to create a custom profile listener
+ */
+class ProfileListener {
+public:
+  /**
+   * @brief Destroy the Base Profile Listener object
+   *
+   */
+  virtual ~ProfileListener() = default;
+
+  /**
+   * @brief A callback function to be called from a profiler
+   *
+   * @param event event key to store the result
+   * @param value time value from the profiler
+   */
+  virtual void onNotify(const EVENT event,
+                        const std::chrono::milliseconds &value) = 0;
+
+  /**
+   * @brief resets the listener to the inital state for a particular key
+   *
+   * @param event event which profiler should notice
+   */
+  virtual void reset(const EVENT event) = 0;
+
+  /**
+   * @brief get the latest result of a event
+   *
+   * @param event event to query the result
+   * @return const std::chrono::milliseconds
+   */
+  virtual const std::chrono::milliseconds result(const EVENT event) = 0;
+
+  /**
+   * @brief report the result
+   *
+   * @param out outstream object to make a report
+   */
+  virtual void report(std::ostream &out) const = 0;
+};
+
+class GenericProfileListener : public ProfileListener {
+public:
+  /**
+   * @brief Destroy the Generic Profile Listener object
+   *
+   */
+  virtual ~GenericProfileListener() = default;
+
+  /**
+   * @copydoc ProfileListener::onNotify(const int event, const
+   * std::chrono::milliseconds &value)
+   */
+  virtual void onNotify(const int event,
+                        const std::chrono::milliseconds &value);
+
+  /**
+   * @copydoc ProfileListener::reset(const int event)
+   */
+  virtual void reset(const int event);
+
+  /**
+   * @copydoc ProfileListener::result(const int event)
+   */
+  virtual const std::chrono::milliseconds result(const int event);
+
+  /**
+   * @copydoc ProfileListener::report(std::ostream &out)
+   */
+  virtual void report(std::ostream &out) const;
+
+private:
+  std::unordered_map<int, std::chrono::milliseconds> time_taken;
+};
+
+/**
+ * @brief   Overriding output stream for layers and it's derived class
+ */
+template <typename T,
+          typename std::enable_if_t<std::is_base_of<ProfileListener, T>::value,
+                                    T> * = nullptr>
+std::ostream &operator<<(std::ostream &out, T &l) {
+  l.report(out);
+  return out;
+}
+
+class Profiler {
+public:
+  Profiler() {}
+
+  Profiler(const Profiler &) = delete;
+
+  Profiler &operator=(const Profiler &) = delete;
+
+  /**
+   *
+   * @brief Get Global app context.
+   *
+   * @return AppContext&
+   */
+  static Profiler &Global();
+
+  /**
+   * @brief start profile
+   *
+   * @param key to record the profile result. Either designated key from enum
+   * or arbitrary key can be used
+   */
+  void start(const EVENT &key);
+
+  /**
+   * @brief end profile and notify to the listeners
+   *
+   * @param key to record the profile result. Either designated key from enum
+   * or arbitrary key can be used
+   */
+  void end(const EVENT &key);
+
+  /**
+   * @brief subscribe a listner to the profiler
+   *
+   * @param listener listener to register, listener must outlive lifetime of
+   * profiler
+   * @param events event listeners are subscribing, if empty listener subscribes
+   * to all events
+   */
+  void subscribe(ProfileListener *listener,
+                 const std::vector<EVENT> &events = {});
+
+private:
+  /**
+   * @brief notify the result
+   *
+   * @param event event to notify
+   * @param value measured value from the profiler
+   */
+  void notify(const EVENT &event, const std::chrono::milliseconds &value);
+
+  std::vector<ProfileListener *>
+    all_event_listeners; /**< listeners subscribed to all events */
+
+  std::unordered_map<EVENT, std::vector<ProfileListener *>>
+    event_listeners; /**< listeners for an events */
+
+  std::unordered_map<EVENT, std::chrono::time_point<std::chrono::steady_clock>>
+    start_time; /**< start_time of the clock */
+};
+
+} // namespace profile
+} // namespace nntrainer
+
+#endif /** __PROFILER_H__ */