Files
rocm-systems/source/lib/core/benchmark/benchmark.hpp
T
Aleksandar Djordjevic 26ae543012 ROCpd support [Part 1] (#279)
- Add rocpd support for
 - cpu_frequency
 - amd_smi
 - sampling
2025-07-28 11:33:52 -04:00

354 regels
11 KiB
C++

// MIT License
//
// Copyright (c) 2022-2025 Advanced Micro Devices, Inc. All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#pragma once
#include <algorithm>
#include <array>
#include <bitset>
#include <chrono>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <limits>
#include <mutex>
#include <sstream>
#include <string>
#include <type_traits>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#include "core/benchmark/category.hpp"
#include "core/debug.hpp"
namespace rocprofsys
{
namespace benchmark
{
namespace
{
template <bool enabled, typename category_enum, category_enum... enabled_categories>
struct benchmark_impl
{
template <category_enum... categories>
struct scope
{
scope(const scope&) = delete;
scope& operator=(const scope&) = delete;
~scope() = default;
protected:
scope() = default;
scope(scope&&) = default;
scope& operator=(scope&&) = default;
};
template <category_enum... categories>
static void start()
{}
template <category_enum... categories>
static void end()
{}
template <category_enum... categories>
[[nodiscard]] static scope<categories...> scoped_trace()
{
return scope<categories...>{};
}
static void init_from_env(const char* = nullptr) {}
static void show_results() {}
};
using tid_t = __pid_t;
struct indexed_category
{
size_t category;
tid_t thread_id;
friend bool operator==(const indexed_category& lhs, const indexed_category& rhs)
{
return lhs.category == rhs.category && lhs.thread_id == rhs.thread_id;
}
};
struct indexed_category_hash
{
size_t operator()(const indexed_category& p) const noexcept
{
std::size_t hash1 = std::hash<size_t>{}(p.category);
std::size_t hash2 = std::hash<size_t>{}(p.thread_id);
return hash1 ^ (hash2 << 1);
}
};
template <typename category_enum, category_enum... enabled_categories>
struct benchmark_impl<true, category_enum, enabled_categories...>
{
static_assert(std::is_enum_v<category_enum>, "category_enum must be an enum");
public:
using clock = std::chrono::high_resolution_clock;
using time_point = clock::time_point;
static constexpr size_t _max_categories = static_cast<size_t>(category_enum::count);
template <category_enum... categories>
struct scope
{
friend benchmark_impl;
public:
scope(const scope&) = delete;
scope& operator=(const scope&) = delete;
~scope() { end<categories...>(); }
protected:
scope() { start<categories...>(); }
scope(scope&&) = default;
scope& operator=(scope&&) = default;
};
template <category_enum... categories>
static void start()
{
static const thread_local auto _thread_id = gettid();
const auto now = clock::now();
std::lock_guard lock(m_mutex);
(..., (is_category_defined<categories>([&] {
if(m_enabled.test(to_index(categories)))
m_started[{ to_index(categories), _thread_id }] = now;
})));
}
template <category_enum... categories>
static void end()
{
static const thread_local auto _thread_id = getpid();
const auto _end_time = clock::now();
std::lock_guard lock(m_mutex);
(..., (is_category_defined<categories>([&] {
if(m_enabled.test(to_index(categories)))
end_category(_end_time, categories, _thread_id);
})));
}
template <category_enum... categories>
[[nodiscard]] static scope<categories...> scoped_trace()
{
return scope<categories...>{};
}
static void init_from_env(const char* envVar = "ROCPROFSYS_BENCHMARK_CATEGORIES")
{
std::lock_guard lock(m_mutex);
const auto* env = std::getenv(envVar);
if(env == nullptr || std::string(env).empty())
{
ROCPROFSYS_WARNING(1, "No BENCHMARK categories specified in environment "
"variable ROCPROFSYS_BENCHMARK_CATEGORIES.\n");
return;
}
std::string _str(env);
std::istringstream ss(_str);
std::string token;
while(std::getline(ss, token, ','))
{
token.erase(0, token.find_first_not_of(" \t"));
token.erase(token.find_last_not_of(" \t") + 1);
for(category_enum cat : compiledCategories)
{
if(to_string(cat) == token)
{
m_enabled.set(to_index(cat));
}
}
}
}
static void show_results()
{
std::lock_guard lock(m_mutex);
std::vector<std::pair<category_enum, result_data>> sorted;
for(category_enum cat : compiledCategories)
{
const auto& data = m_results[to_index(cat)];
if(data.count > 0)
{
sorted.emplace_back(cat, data);
}
}
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
return a.second.total_time > b.second.total_time;
});
constexpr uint32_t _category = 30;
constexpr uint32_t _calls = 8;
constexpr uint32_t _total = 12;
constexpr uint32_t _avg = 10;
constexpr uint32_t _min = 10;
constexpr uint32_t _max = 10;
std::cout << "\033[32m"
<< std::string(_category + _calls + _total + _avg + _min + _max, '=')
<< "\n";
std::cout << "Benchmark Results (Sorted by Total Time):\n";
std::cout << std::string(_category + _calls + _total + _avg + _min + _max, '-')
<< "\n";
std::cout << std::left << std::setw(_category) << "Category" << std::right
<< std::setw(_calls) << "Calls" << std::setw(_total) << "Total(ms)"
<< std::setw(_avg) << "Avg(us)" << std::setw(_min) << "Min(us)"
<< std::setw(_max) << "Max(us)" << "\n";
std::cout << std::string(_category + _calls + _total + _avg + _min + _max, '-')
<< "\n";
for(const auto& [cat, data] : sorted)
{
double totalMs = static_cast<double>(data.total_time) / 1000.0;
double avgUs = static_cast<double>(data.total_time) / data.count;
std::cout << std::left << std::setw(_category) << to_string(cat) << std::right
<< std::setw(_calls) << data.count << std::setw(_total)
<< std::fixed << std::setprecision(3) << totalMs << std::setw(_avg)
<< std::fixed << std::setprecision(1) << avgUs << std::setw(_min)
<< data.min_time << std::setw(_max) << data.max_time << "\n";
}
std::cout << std::string(_category + _calls + _total + _avg + _min + _max, '=')
<< "\033[0m" << "\n\n";
}
private:
struct result_data
{
uint64_t total_time = 0;
size_t count = 0;
uint64_t min_time = std::numeric_limits<uint64_t>::max();
uint64_t max_time = std::numeric_limits<uint64_t>::min();
void update(uint64_t duration)
{
total_time += duration;
count += 1;
if(duration < min_time) min_time = duration;
if(duration > max_time) max_time = duration;
}
};
static constexpr size_t to_index(category_enum cat)
{
return static_cast<size_t>(cat);
}
static void end_category(const time_point& end_time, category_enum cat,
const tid_t thread_id)
{
const size_t _idx = to_index(cat);
auto _it = m_started.find({ _idx, thread_id });
if(_it == m_started.end())
{
ROCPROFSYS_WARNING(1, "Benchmark error: missing start time for category!\n");
return;
}
auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(end_time - _it->second)
.count();
m_started.erase(_it);
m_results[_idx].update(duration);
}
template <category_enum Cat, typename Func>
static constexpr void is_category_defined(Func&& f)
{
if constexpr(((Cat == enabled_categories) || ...))
{
f();
}
}
static constexpr std::array<category_enum, sizeof...(enabled_categories)>
compiledCategories = { enabled_categories... };
static inline std::unordered_map<indexed_category, time_point, indexed_category_hash>
m_started;
static inline std::array<result_data, _max_categories> m_results{};
static inline std::bitset<_max_categories> m_enabled;
static inline std::mutex m_mutex;
};
#ifdef ROCPROFSYS_ENABLE_BENCHMARK
using _benchmark_impl = benchmark::benchmark_impl<
static_cast<bool>(ROCPROFSYS_ENABLE_BENCHMARK), benchmark::category,
benchmark::category::kernel_dispatch, benchmark::category::memory_copy,
benchmark::category::memory_allocate, benchmark::category::db_entry_kernel_dispatch,
benchmark::category::db_entry_memory_copy,
benchmark::category::db_entry_memory_allocate,
benchmark::category::perfetto_kernel_dispatch,
benchmark::category::sdk_tool_buffered_tracing>;
#else
using _benchmark_impl = benchmark::benchmark_impl<false, benchmark::category>;
#endif
} // namespace
template <category... categories>
void
start()
{
_benchmark_impl::template start<categories...>();
}
template <category... categories>
void
end()
{
_benchmark_impl::template end<categories...>();
}
template <category... categories>
[[nodiscard]] auto
scoped_trace()
{
return _benchmark_impl::template scoped_trace<categories...>();
}
inline void
init_from_env(const char* envVar = "BENCHMARK_CATEGORIES")
{
_benchmark_impl::init_from_env(envVar);
}
inline void
show_results()
{
_benchmark_impl::show_results();
}
} // namespace benchmark
} // namespace rocprofsys