From: Jihoon Lee Date: Wed, 9 Dec 2020 04:09:34 +0000 (+0900) Subject: [Profiler] Separate Profiler for wider use X-Git-Tag: accepted/tizen/unified/20201217.124219~8 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=70f873c149eac56637f3962956242b6c6b1d849c;p=platform%2Fcore%2Fml%2Fnntrainer.git [Profiler] Separate Profiler for wider use 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 --- diff --git a/nntrainer/models/neuralnet.cpp b/nntrainer/models/neuralnet.cpp index 9700ace..8a4505e 100644 --- a/nntrainer/models/neuralnet.cpp +++ b/nntrainer/models/neuralnet.cpp @@ -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(model_graph.Sorted.back().layer) ->forwarding(); diff --git a/nntrainer/models/neuralnet.h b/nntrainer/models/neuralnet.h index c4ca37f..b78797f 100644 --- a/nntrainer/models/neuralnet.h +++ b/nntrainer/models/neuralnet.h @@ -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 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( - end - iter->second); - } - - private: - std::unordered_map> - start_time; /**< start_time of the clock */ - std::unordered_map time_taken; - }; - -public: - /** - * @brief Get the Profile Result - * - * @param key key to recorder the profile - * @return std::chrono::time_point - */ - 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 index 0000000..327635b --- /dev/null +++ b/nntrainer/utils/profiler.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +/** + * Copyright (C) 2020 Jihoon Lee + * + * @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 + * @bug No known bugs except for NYI items + * + */ +#include + +#include + +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(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 &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{}; + } + 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 index 0000000..f563f8a --- /dev/null +++ b/nntrainer/utils/profiler.h @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: Apache-2.0 +/** + * Copyright (C) 2020 Jihoon Lee + * + * @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 + * @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 +#include +#include +#include +#include + +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 time_taken; +}; + +/** + * @brief Overriding output stream for layers and it's derived class + */ +template ::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 &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 + all_event_listeners; /**< listeners subscribed to all events */ + + std::unordered_map> + event_listeners; /**< listeners for an events */ + + std::unordered_map> + start_time; /**< start_time of the clock */ +}; + +} // namespace profile +} // namespace nntrainer + +#endif /** __PROFILER_H__ */