diff --git a/CMakeLists.txt b/CMakeLists.txt index 00015af86f..2d8f6c3f81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,17 +423,11 @@ if(REALM_ENABLE_CUDA OR (REALM_ENABLE_HIP AND CMAKE_HIP_PLATFORM STREQUAL "nvidi PATH_SUFFIXES include ../include extras/CUPTI/include ../extras/CUPTI/include ) - # Deal with the library name change in older versions of cmake. - # - # nvtx3 was added in cmake 3.25, and while later versions (at least 3.30+) claim that - # the CUDA::nvtx3 and CUDA::nvToolsExt targets are mutually exclusive, they don't - # appear to be so in earlier versions. So if we have nvtx3, prefer that, otherwise - # fall back to nvToolsExt. + # We use the header-only NVTX v3 C++ API, which is exposed via the + # CUDA::nvtx3 target (added in cmake 3.25). Require it; otherwise disable NVTX. set(REALM_USE_NVTX ${REALM_ENABLE_NVTX}) if(TARGET CUDA::nvtx3) list(APPEND REALM_LIBRARIES CUDA::nvtx3) - elseif(TARGET CUDA::nvToolsExt) - list(APPEND REALM_LIBRARIES CUDA::nvToolsExt) else() # Silently disable NVTX if we cannot find it set(REALM_USE_NVTX OFF) diff --git a/src/realm/cuda/cuda_module.cc b/src/realm/cuda/cuda_module.cc index 4170b85814..df59952e7c 100644 --- a/src/realm/cuda/cuda_module.cc +++ b/src/realm/cuda/cuda_module.cc @@ -1531,6 +1531,11 @@ namespace Realm { tte = &it->second; } +#ifdef REALM_USE_NVTX + std::string nvtx_msg = stringbuilder() << "task " << func_id; + nvtxUniqueRange nvtx_range(nvtx_category.get(), nvtx_msg.c_str()); +#endif + if(tte->stream_aware_fnptr) { // shouldn't be here without a valid stream assert(ThreadLocal::current_gpu_stream != nullptr); diff --git a/src/realm/nvtx.cc b/src/realm/nvtx.cc index d2bd4b51b2..548a6f8a74 100644 --- a/src/realm/nvtx.cc +++ b/src/realm/nvtx.cc @@ -17,134 +17,123 @@ #include "realm/nvtx.h" -#include +#include #include +#include +#include "realm/atomics.h" #ifdef REALM_ON_WINDOWS #include #else +#include +#include #include #endif namespace Realm { - struct realm_nvtx_domain { - static constexpr char const *name{"Realm"}; - }; + // Definitions of the predefined category handles declared in the header. + NvtxCategory *nvtx_amsg = nullptr; + NvtxCategory *nvtx_bgwork = nullptr; +#ifdef REALM_USE_CUDA + NvtxCategory *nvtx_cuda = nullptr; +#endif +#ifdef REALM_USE_HIP + NvtxCategory *nvtx_hip = nullptr; +#endif +#ifdef REALM_USE_GASNET1 + NvtxCategory *nvtx_gasnet1 = nullptr; +#endif +#ifdef REALM_USE_GASNETEX + NvtxCategory *nvtx_gasnetex = nullptr; +#endif +#ifdef REALM_USE_MPI + NvtxCategory *nvtx_mpi = nullptr; +#endif +#ifdef REALM_USE_OPENMP + NvtxCategory *nvtx_openmp = nullptr; +#endif +#ifdef REALM_USE_PYTHON + NvtxCategory *nvtx_python = nullptr; +#endif - struct nvtx_category_id_color { + // Static description of a predefined category: its id, default color, and the + // global handle to point at the created object. `slot` is a reference to one + // of the `nvtx_*` pointers defined above. + struct nvtx_category_def { uint32_t id; - uint32_t color; + nvtx3::color color; + std::reference_wrapper slot; }; - static std::map nvtx_categories_predefined = { - {"amsg", {1, nvtx_color::red}}, - {"bgwork", {2, nvtx_color::blue}}, + static const std::map nvtx_categories_predefined = { + {"amsg", {1, nvtx_color::red, std::ref(nvtx_amsg)}}, + {"bgwork", {2, nvtx_color::blue, std::ref(nvtx_bgwork)}}, #ifdef REALM_USE_CUDA - {"cuda", {100, nvtx_color::green}}, + {"cuda", {100, nvtx_color::green, std::ref(nvtx_cuda)}}, #endif #ifdef REALM_USE_HIP - {"hip", {101, nvtx_color::purple}}, + {"hip", {101, nvtx_color::purple, std::ref(nvtx_hip)}}, #endif #ifdef REALM_USE_GASNET1 - {"gasnet1", {102, nvtx_color::lawn_green}}, + {"gasnet1", {102, nvtx_color::lawn_green, std::ref(nvtx_gasnet1)}}, #endif #ifdef REALM_USE_GASNETEX - {"gasnetex", {103, nvtx_color::cyan}}, + {"gasnetex", {103, nvtx_color::cyan, std::ref(nvtx_gasnetex)}}, #endif #ifdef REALM_USE_MPI - {"mpi", {104, nvtx_color::maroon}}, + {"mpi", {104, nvtx_color::maroon, std::ref(nvtx_mpi)}}, #endif #ifdef REALM_USE_OPENMP - {"openmp", {105, nvtx_color::navy}}, + {"openmp", {105, nvtx_color::navy, std::ref(nvtx_openmp)}}, #endif #ifdef REALM_USE_PYTHON - {"python", {106, nvtx_color::magenta}}, + {"python", {106, nvtx_color::magenta, std::ref(nvtx_python)}}, #endif }; - thread_local std::map *nvtx_categories; - - static nvtxDomainHandle_t nvtxRealmDomain = nullptr; + // Owns the category objects created at init; cleared at finalize. The global + // `nvtx_*` handles point into these. + static std::vector> nvtx_owned_categories; static std::vector enabled_nvtx_modules; - static inline NvtxCategory *find_category_by_name(const std::string &name) - { - std::map::iterator it = nvtx_categories->find(name); - if(it != nvtx_categories->end()) { - return it->second; - } else { - return nullptr; - } - } - - //////////////////////////////////////////////////////////////////////// - // - // class nvtxCategory - - NvtxCategory::NvtxCategory(const std::string &category_name, uint32_t category_id, - uint32_t color) - : name(category_name) - { - // name the category - nvtxDomainNameCategoryA(nvtxRealmDomain, category_id, category_name.c_str()); - - // create nvtx event attribute and set values - memset(&nvtx_event, 0, NVTX_EVENT_ATTRIB_STRUCT_SIZE); - nvtx_event.version = NVTX_VERSION; - nvtx_event.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE; - nvtx_event.category = category_id; - nvtx_event.messageType = NVTX_MESSAGE_TYPE_ASCII; - nvtx_event.message.ascii = ""; - nvtx_event.payloadType = NVTX_PAYLOAD_TYPE_INT32; - nvtx_event.payload.iValue = 0; - nvtx_event.colorType = NVTX_COLOR_ARGB; - nvtx_event.color = color; - } + static atomic nvtx_proc_starting_category_id{1000}; - //////////////////////////////////////////////////////////////////////// - // - // class nvtxScopedRange - - nvtxScopedRange::nvtxScopedRange(NvtxCategory *category, char const *message, - int32_t payload) - { - category->nvtx_event.message.ascii = message; - category->nvtx_event.payload.iValue = payload; - nvtxDomainRangePushEx(nvtxRealmDomain, &(category->nvtx_event)); - } + // Palette of visually distinct colors handed out by nvtx_get_next_color(). + static constexpr nvtx3::color nvtx_auto_color_palette[] = { + nvtx_color::red, nvtx_color::green, nvtx_color::blue, + nvtx_color::purple, nvtx_color::cyan, nvtx_color::maroon, + nvtx_color::navy, nvtx_color::magenta, nvtx_color::yellow, + nvtx_color::teal, nvtx_color::olive, nvtx_color::lawn_green, + }; + static atomic nvtx_next_color_index{0}; - nvtxScopedRange::nvtxScopedRange(const std::string &name, char const *message, - int32_t payload) + // Create the category object, take ownership, and point its global handle at it. + static void create_category(const std::string &name, const nvtx_category_def &def) { - NvtxCategory *category = find_category_by_name(name); - if(category) { - category->nvtx_event.message.ascii = message; - category->nvtx_event.payload.iValue = payload; - nvtxDomainRangePushEx(nvtxRealmDomain, &(category->nvtx_event)); - } + nvtx_owned_categories.push_back( + std::make_unique(name, def.id, def.color)); + def.slot.get() = nvtx_owned_categories.back().get(); } - nvtxScopedRange::~nvtxScopedRange() { nvtxDomainRangePop(nvtxRealmDomain); } - void init_nvtx_thread(const char *thread_name) { #ifdef REALM_ON_WINDOWS - nvtxNameOsThread(GetCurrentThreadId(), thread_name) + nvtxNameOsThread(GetCurrentThreadId(), thread_name); #else - nvtxNameOsThread(pthread_self(), thread_name); + // NVTX wants the OS-native (kernel) thread id, which on Linux is gettid(), + nvtxNameOsThread(static_cast(syscall(SYS_gettid)), thread_name); #endif + } - nvtx_categories = new std::map(); - nvtx_categories->clear(); + void init_nvtx(std::vector &nvtx_modules) + { + enabled_nvtx_modules = nvtx_modules; - if(enabled_nvtx_modules.size() == 1 and enabled_nvtx_modules[0] == "all") { + if(enabled_nvtx_modules.size() == 1 && enabled_nvtx_modules[0] == "all") { // handle -ll:nvtx_modules all - std::map::const_iterator it; - for(it = nvtx_categories_predefined.cbegin(); - it != nvtx_categories_predefined.cend(); it++) { - nvtx_categories->insert(std::pair( - it->first, new NvtxCategory(it->first, it->second.id, it->second.color))); + for(const auto &entry : nvtx_categories_predefined) { + create_category(entry.first, entry.second); } } else { for(const std::string &name : enabled_nvtx_modules) { @@ -152,123 +141,39 @@ namespace Realm { std::cerr << "If all specified, then no other modules are needed." << std::endl; abort(); } - std::map::const_iterator it = + std::map::const_iterator it = nvtx_categories_predefined.find(name); if(it == nvtx_categories_predefined.end()) { std::cerr << "Unable to find specified nvtx module: " << name << std::endl; abort(); } - nvtx_categories->insert(std::pair( - name, new NvtxCategory(name, it->second.id, it->second.color))); + create_category(it->first, it->second); } } - } - void finalize_nvtx_thread(void) - { - std::map::iterator it; - for(it = nvtx_categories->begin(); it != nvtx_categories->end(); it++) { - assert(it->second != nullptr); - delete it->second; - } - delete nvtx_categories; - } - - void init_nvtx(std::vector &nvtx_modules) - { - enabled_nvtx_modules = nvtx_modules; - nvtxInitialize(nullptr); - nvtxRealmDomain = nvtxDomainCreateA(realm_nvtx_domain::name); init_nvtx_thread("MainThread"); } void finalize_nvtx(void) { - nvtxDomainDestroy(nvtxRealmDomain); - finalize_nvtx_thread(); - } - - void nvtx_range_push(NvtxCategory *category, const char *message, uint32_t color, - int32_t payload) - { - uint32_t origin_color; - if(color != nvtx_color::white) { - origin_color = category->nvtx_event.color; - category->nvtx_event.color = color; - } - category->nvtx_event.message.ascii = message; - category->nvtx_event.payload.iValue = payload; - nvtxDomainRangePushEx(nvtxRealmDomain, &(category->nvtx_event)); - if(color != nvtx_color::white) { - category->nvtx_event.color = origin_color; + // The nvtx3 domain is intentionally never destroyed (see nvtx3 docs). Drop + // the owned categories and reset the global handles so none dangle. + nvtx_owned_categories.clear(); + for(const auto &entry : nvtx_categories_predefined) { + entry.second.slot.get() = nullptr; } } - void nvtx_range_push(const std::string &name, const char *message, uint32_t color, - int32_t payload) + uint32_t nvtx_get_next_category_id(void) { - NvtxCategory *category = find_category_by_name(name); - if(category) { - nvtx_range_push(category, message, color, payload); - } + return nvtx_proc_starting_category_id.fetch_add(1); } - void nvtx_range_pop(void) { nvtxDomainRangePop(nvtxRealmDomain); } - - nvtxRangeId_t nvtx_range_start(NvtxCategory *category, const char *message, - uint32_t color, int32_t payload) + nvtx3::color nvtx_get_next_color(void) { - uint32_t origin_color; - if(color != nvtx_color::white) { - origin_color = category->nvtx_event.color; - category->nvtx_event.color = color; - } - category->nvtx_event.message.ascii = message; - category->nvtx_event.payload.iValue = payload; - nvtxRangeId_t id = nvtxDomainRangeStartEx(nvtxRealmDomain, &(category->nvtx_event)); - if(color != nvtx_color::white) { - category->nvtx_event.color = origin_color; - } - return id; - } - - nvtxRangeId_t nvtx_range_start(const std::string &name, const char *message, - uint32_t color, int32_t payload) - { - NvtxCategory *category = find_category_by_name(name); - if(category) { - return nvtx_range_start(category, message, color, payload); - } else { - return 0; - } - } - - void nvtx_range_end(nvtxRangeId_t id) { nvtxDomainRangeEnd(nvtxRealmDomain, id); } - - void nvtx_mark(NvtxCategory *category, const char *message, uint32_t color, - int32_t payload) - { - uint32_t origin_color; - if(color != nvtx_color::white) { - origin_color = category->nvtx_event.color; - category->nvtx_event.color = color; - } - category->nvtx_event.message.ascii = message; - category->nvtx_event.color = color; - category->nvtx_event.payload.iValue = payload; - nvtxDomainMarkEx(nvtxRealmDomain, &(category->nvtx_event)); - if(color != nvtx_color::white) { - category->nvtx_event.color = origin_color; - } - } - - void nvtx_mark(const std::string &name, const char *message, uint32_t color, - int32_t payload) - { - NvtxCategory *category = find_category_by_name(name); - if(category) { - nvtx_mark(category, message, color, payload); - } + constexpr size_t n = + sizeof(nvtx_auto_color_palette) / sizeof(nvtx_auto_color_palette[0]); + return nvtx_auto_color_palette[nvtx_next_color_index.fetch_add(1) % n]; } }; // namespace Realm diff --git a/src/realm/nvtx.h b/src/realm/nvtx.h index c73fbc9376..eedee19cb8 100644 --- a/src/realm/nvtx.h +++ b/src/realm/nvtx.h @@ -20,107 +20,227 @@ #include "realm/realm_config.h" +// Realm's NVTX support is built on the nvtx3 C++ API. The C-only headers +// () are intentionally not used as a fallback. #if __has_include() #include -#elif __has_include() -#include #else -#error "Configuration failed to find suitable NVTX headers" +#error "Realm NVTX support requires the nvtx3 C++ headers ()" #endif #include +#include #include #include namespace Realm { - struct NvtxARGB { - constexpr NvtxARGB(uint8_t red_, uint8_t green_, uint8_t blue_, - uint8_t alpha_ = 0xFF) noexcept - : red{red_} - , green{green_} - , blue{blue_} - , alpha{alpha_} - {} - constexpr uint32_t to_uint(void) const - { - return uint32_t{alpha} << 24 | uint32_t{red} << 16 | uint32_t{green} << 8 | - uint32_t{blue}; - } - uint8_t const red{}; - uint8_t const green{}; - uint8_t const blue{}; - uint8_t const alpha{}; - }; - - enum nvtx_color : uint32_t - { - white = NvtxARGB(255, 255, 255).to_uint(), - red = NvtxARGB(255, 0, 0).to_uint(), - green = NvtxARGB(0, 255, 0).to_uint(), - blue = NvtxARGB(0, 0, 255).to_uint(), - purple = NvtxARGB(128, 0, 128).to_uint(), - lawn_green = NvtxARGB(124, 252, 0).to_uint(), - cyan = NvtxARGB(0, 255, 255).to_uint(), - maroon = NvtxARGB(128, 0, 0).to_uint(), - navy = NvtxARGB(0, 0, 128).to_uint(), - magenta = NvtxARGB(255, 0, 255).to_uint(), - yellow = NvtxARGB(255, 255, 0).to_uint(), - gray = NvtxARGB(128, 128, 128).to_uint(), - teal = NvtxARGB(0, 128, 128).to_uint(), - olive = NvtxARGB(128, 128, 0).to_uint(), + // Named colors for NVTX events, as nvtx3::color values (fully opaque ARGB, + // since rgb{} fills the alpha channel). Pass one of these to an annotation + // call, or pass std::nullopt to fall back to the category's default color. + namespace nvtx_color { + inline constexpr nvtx3::color white{nvtx3::rgb{255, 255, 255}}; + inline constexpr nvtx3::color red{nvtx3::rgb{255, 0, 0}}; + inline constexpr nvtx3::color green{nvtx3::rgb{0, 255, 0}}; + inline constexpr nvtx3::color blue{nvtx3::rgb{0, 0, 255}}; + inline constexpr nvtx3::color purple{nvtx3::rgb{128, 0, 128}}; + inline constexpr nvtx3::color lawn_green{nvtx3::rgb{124, 252, 0}}; + inline constexpr nvtx3::color cyan{nvtx3::rgb{0, 255, 255}}; + inline constexpr nvtx3::color maroon{nvtx3::rgb{128, 0, 0}}; + inline constexpr nvtx3::color navy{nvtx3::rgb{0, 0, 128}}; + inline constexpr nvtx3::color magenta{nvtx3::rgb{255, 0, 255}}; + inline constexpr nvtx3::color yellow{nvtx3::rgb{255, 255, 0}}; + inline constexpr nvtx3::color gray{nvtx3::rgb{128, 128, 128}}; + inline constexpr nvtx3::color teal{nvtx3::rgb{0, 128, 128}}; + inline constexpr nvtx3::color olive{nvtx3::rgb{128, 128, 0}}; + } // namespace nvtx_color + + // The single NVTX domain that groups all of Realm's annotations. This is an + // nvtx3 domain tag type: it just needs a static `name` member. + struct realm_nvtx_domain { + static constexpr char const *name{"Realm"}; }; + // Sentinel payload meaning "no payload". NVTX ignores the payload value when + // its type is NVTX_PAYLOAD_UNKNOWN, so events built with this carry no + // payload at all (rather than an int32 value of 0). Used as the default for + // the annotation calls below so callers can simply omit the payload. + inline constexpr nvtx3::payload nvtx_no_payload{NVTX_PAYLOAD_UNKNOWN, + nvtx3::payload::value_type{}}; + + // Metadata for one NVTX category. Immutable after construction. Categories are + // created only during init_nvtx (on the main thread, before worker threads are + // started), so no synchronization is needed. + // + // `category` carries the category id (via get_id()) and, on construction, + // registers the id<->name association with NVTX so tools display the name. struct NvtxCategory { - NvtxCategory(const std::string &category_name, uint32_t category_id, uint32_t color); - const std::string name; - nvtxEventAttributes_t nvtx_event; - }; - - struct nvtxScopedRange { - nvtxScopedRange(NvtxCategory *category, char const *message, int32_t payload); - nvtxScopedRange(const std::string &name, char const *message, int32_t payload); - ~nvtxScopedRange(); + NvtxCategory(const std::string &category_name, uint32_t category_id, + nvtx3::color color) + : name(category_name) + , category(category_id, category_name.c_str()) + , default_color(color) + {} + std::string name; + nvtx3::named_category_in category; + nvtx3::color default_color; }; - static constexpr uint32_t nvtx_proc_starting_category_id = 1000; + // Handles to the predefined categories. Each is non-null only if its module + // was enabled via -ll:nvtx_modules; otherwise it stays null and annotation + // calls on it are cheap no-ops. Call sites pass these directly, with no name + // lookup. Defined in nvtx.cc. + extern NvtxCategory *nvtx_amsg; + extern NvtxCategory *nvtx_bgwork; +#ifdef REALM_USE_CUDA + extern NvtxCategory *nvtx_cuda; +#endif +#ifdef REALM_USE_HIP + extern NvtxCategory *nvtx_hip; +#endif +#ifdef REALM_USE_GASNET1 + extern NvtxCategory *nvtx_gasnet1; +#endif +#ifdef REALM_USE_GASNETEX + extern NvtxCategory *nvtx_gasnetex; +#endif +#ifdef REALM_USE_MPI + extern NvtxCategory *nvtx_mpi; +#endif +#ifdef REALM_USE_OPENMP + extern NvtxCategory *nvtx_openmp; +#endif +#ifdef REALM_USE_PYTHON + extern NvtxCategory *nvtx_python; +#endif - // called by each kernel thread to init thread local variables. + // Called by each kernel thread to name the OS thread in NVTX tools. void init_nvtx_thread(const char *thread_name); - // called by RuntimeImpl::configure_from_command_line from the main thread to init nvtx - // and its thread local variables. + // Called by RuntimeImpl::configure_from_command_line on the main thread to + // create the enabled categories. Must run before worker threads start. void init_nvtx(std::vector &nvtx_modules); - // called by each kernel thread to delete thread local variables. - void finalize_nvtx_thread(void); - - // called by RuntimeImpl::wait_for_shutdown from the main thread to finalize nvtx - // and delete its thread local variables. + // Called by RuntimeImpl::wait_for_shutdown on the main thread. void finalize_nvtx(void); - // TODO(@Wei Wu): template it wih type T for payload - void nvtx_range_push(NvtxCategory *category, const char *message, - uint32_t color = nvtx_color::white, int32_t payload = 0); + // Returns the next available category id. + uint32_t nvtx_get_next_category_id(void); + + // Returns the next auto-assigned color, cycling through a fixed palette of + // visually distinct colors. Handy for giving dynamically created categories + // distinguishable colors without picking one by hand. + nvtx3::color nvtx_get_next_color(void); + + // Internal helpers for the inline annotation calls below. Kept in the header + // (inline) so the thin wrappers fully inline at call sites; in particular the + // common "disabled module" case (null category) collapses to nothing. + + // Build a fresh event on the stack for each annotation. NVTX consumes the + // attributes synchronously, so there is no need to keep one around. An empty + // `color` means "use the category's default color". + inline nvtx3::event_attributes nvtx_make_event(const NvtxCategory &category, + const char *message, + std::optional color, + nvtx3::payload payload) + { + return nvtx3::event_attributes{category.category, + color.value_or(category.default_color), payload, + nvtx3::message{message}}; + } + + inline nvtxDomainHandle_t nvtx_realm_domain() + { + return nvtx3::domain::get(); + } + + // RAII nested range. A null `category` (disabled module) pushes nothing and + // the destructor pops nothing, keeping the NVTX range stack balanced. + struct [[nodiscard]] nvtxScopedRange { + nvtxScopedRange(NvtxCategory *category, char const *message, + nvtx3::payload payload = nvtx_no_payload) noexcept + : active(false) + { + if(category) { + nvtx3::event_attributes attr = + nvtx_make_event(*category, message, std::nullopt, payload); + nvtxDomainRangePushEx(nvtx_realm_domain(), attr.get()); + active = true; + } + } + ~nvtxScopedRange() + { + if(active) { + nvtxDomainRangePop(nvtx_realm_domain()); + } + } - void nvtx_range_push(const std::string &name, const char *message, - uint32_t color = nvtx_color::white, int32_t payload = 0); + private: + bool active; + }; - void nvtx_range_pop(void); + inline void nvtx_range_push(NvtxCategory *category, const char *message, + std::optional color = std::nullopt, + nvtx3::payload payload = nvtx_no_payload) + { + if(!category) { + return; + } + nvtx3::event_attributes attr = nvtx_make_event(*category, message, color, payload); + nvtxDomainRangePushEx(nvtx_realm_domain(), attr.get()); + } - // TODO(@Wei Wu): template it wih type T for payload - nvtxRangeId_t nvtx_range_start(NvtxCategory *category, const char *message, - uint32_t color = nvtx_color::white, int32_t payload = 0); - nvtxRangeId_t nvtx_range_start(const std::string &name, const char *message, - uint32_t color = nvtx_color::white, int32_t payload = 0); + inline void nvtx_range_pop(void) { nvtxDomainRangePop(nvtx_realm_domain()); } - void nvtx_range_end(nvtxRangeId_t id); + inline nvtx3::range_handle nvtx_range_start(NvtxCategory *category, + const char *message, + std::optional color = std::nullopt, + nvtx3::payload payload = nvtx_no_payload) + { + if(!category) { + return nullptr; + } + nvtx3::event_attributes attr = nvtx_make_event(*category, message, color, payload); + return nvtx3::start_range_in(attr); + } - // TODO(@Wei Wu): template it wih type T for payload - void nvtx_mark(NvtxCategory *category, const char *message, - uint32_t color = nvtx_color::white, int32_t payload = 0); - void nvtx_mark(const std::string &name, const char *message, - uint32_t color = nvtx_color::white, int32_t payload = 0); + inline void nvtx_range_end(nvtx3::range_handle id) + { + if(id) { + nvtx3::end_range_in(id); + } + } + + // RAII range built on the Start/End API (nvtxDomainRangeStartEx/End) rather + // than Push/Pop. Start/End ranges are process-scoped, so NVTX tools display + // them grouped by category under a single domain row (and, for ranges whose + // start and end land on the same thread, also under that thread) instead of + // only nesting under the originating thread. A null `category` (disabled + // module) starts nothing and the destructor ends nothing. + struct [[nodiscard]] nvtxUniqueRange { + explicit nvtxUniqueRange(NvtxCategory *category, char const *message, + nvtx3::payload payload = nvtx_no_payload) + : handle(nvtx_range_start(category, message, std::nullopt, payload)) + {} + ~nvtxUniqueRange() { nvtx_range_end(handle); } + + nvtxUniqueRange(const nvtxUniqueRange &) = delete; + nvtxUniqueRange &operator=(const nvtxUniqueRange &) = delete; + + private: + nvtx3::range_handle handle; + }; + + inline void nvtx_mark(NvtxCategory *category, const char *message, + std::optional color = std::nullopt, + nvtx3::payload payload = nvtx_no_payload) + { + if(!category) { + return; + } + nvtx3::event_attributes attr = nvtx_make_event(*category, message, color, payload); + nvtx3::mark_in(attr); + } }; // namespace Realm diff --git a/src/realm/proc_impl.cc b/src/realm/proc_impl.cc index 0f7f5fabbf..1b1670aba8 100644 --- a/src/realm/proc_impl.cc +++ b/src/realm/proc_impl.cc @@ -475,7 +475,14 @@ namespace Realm { , me(_me) , kind(_kind) , num_cores(_num_cores) - {} + { +#ifdef REALM_USE_NVTX + std::string nvtx_name = + stringbuilder() << Processor::get_kind_name(kind) << " " << me; + nvtx_category = std::make_unique( + nvtx_name, nvtx_get_next_category_id(), nvtx_get_next_color()); +#endif + } ProcessorImpl::~ProcessorImpl(void) {} @@ -1151,6 +1158,11 @@ namespace Realm { log_taskreg.debug() << "task " << func_id << " executing on " << me << ": " << ((void *)(tte.fnptr)); +#ifdef REALM_USE_NVTX + std::string nvtx_msg = stringbuilder() << "task " << func_id; + nvtxUniqueRange nvtx_range(nvtx_category.get(), nvtx_msg.c_str()); +#endif + (tte.fnptr)(task_args.base(), task_args.size(), tte.user_data.base(), tte.user_data.size(), me); } diff --git a/src/realm/proc_impl.h b/src/realm/proc_impl.h index d8a3ae1078..f914a343f5 100644 --- a/src/realm/proc_impl.h +++ b/src/realm/proc_impl.h @@ -37,6 +37,11 @@ #include "realm/threads.h" #include "realm/codedesc.h" +#ifdef REALM_USE_NVTX +#include "realm/nvtx.h" +#include +#endif + namespace Realm { class ProcessorGroupImpl; @@ -134,6 +139,12 @@ namespace Realm { Processor me; Processor::Kind kind; int num_cores; +#ifdef REALM_USE_NVTX + // Per-processor NVTX category, created at construction (on the main thread, + // before worker threads start). Tasks executed on this processor are + // annotated under it so they group per processor in NVTX tools. + std::unique_ptr nvtx_category; +#endif }; // generic local task processor - subclasses must create and configure a task diff --git a/src/realm/tasks.cc b/src/realm/tasks.cc index 308a0a6ed0..f4b503bbb0 100644 --- a/src/realm/tasks.cc +++ b/src/realm/tasks.cc @@ -21,6 +21,7 @@ #include "realm/runtime_impl.h" #include "realm/proc_impl.h" +#include "realm/utils.h" #if defined(REALM_USE_CACHING_ALLOCATOR) #include "realm/caching_allocator.h" @@ -1393,6 +1394,14 @@ namespace Realm { void KernelThreadTaskScheduler::thread_starting(Thread *thread) { +#ifdef REALM_USE_NVTX + // Name this OS (kernel) worker thread after the processor it serves so the + // proc's tasks show up under a clearly labeled row in NVTX tools. + std::string nvtx_thread_name = stringbuilder() + << "Realm " << Processor::get_kind_name(proc.kind()) << " " << proc; + init_nvtx_thread(nvtx_thread_name.c_str()); +#endif + log_sched.info() << "scheduler worker started: sched=" << this << " worker=" << thread; @@ -1673,6 +1682,14 @@ namespace Realm { void UserThreadTaskScheduler::host_thread_loop(void) { +#ifdef REALM_USE_NVTX + // With user threading the worker user threads are multiplexed on this host + // OS thread, so name the host thread after the processor it serves. + std::string nvtx_thread_name = stringbuilder() + << "Realm " << Processor::get_kind_name(proc.kind()) << " " << proc; + init_nvtx_thread(nvtx_thread_name.c_str()); +#endif + log_sched.debug() << "host thread started: sched=" << this << " thread=" << Thread::self(); AutoLock al(lock); diff --git a/src/realm/threads.cc b/src/realm/threads.cc index aff85cf930..ce4161dd66 100644 --- a/src/realm/threads.cc +++ b/src/realm/threads.cc @@ -838,7 +838,7 @@ namespace Realm { /*static*/ void *KernelThread::pthread_entry(void *data) { #ifdef REALM_USE_NVTX - init_nvtx_thread("RealmKernalThread"); + init_nvtx_thread("RealmKernelThread"); #endif KernelThread *thread = (KernelThread *)data; @@ -910,10 +910,6 @@ namespace Realm { if(thread->scheduler) thread->scheduler->thread_terminating(thread); -#ifdef REALM_USE_NVTX - finalize_nvtx_thread(); -#endif - return 0; } #endif @@ -922,7 +918,7 @@ namespace Realm { /*static*/ DWORD WINAPI KernelThread::winthread_entry(LPVOID data) { #ifdef REALM_USE_NVTX - init_nvtx_thread("RealmKernalThread"); + init_nvtx_thread("RealmKernelThread"); #endif KernelThread *thread = (KernelThread *)data; @@ -946,10 +942,6 @@ namespace Realm { if(thread->scheduler) thread->scheduler->thread_terminating(thread); -#ifdef REALM_USE_NVTX - finalize_nvtx_thread(); -#endif - return 0; } #endif