basedOpinionated utility library |
git clone git://git.dimitrijedobrota.com/based.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING |
instrumentation.hpp (6798B)
0 #pragma once 1 2 #include <algorithm> 3 #include <array> 4 #include <chrono> 5 #include <cmath> 6 #include <concepts> 7 #include <cstddef> 8 #include <cstdint> 9 #include <format> 10 #include <iostream> 11 #include <numbers> 12 #include <numeric> 13 #include <random> 14 #include <vector> 15 16 #include "based/enum.hpp" 17 18 namespace based 19 { 20 21 class table 22 { 23 public: 24 explicit table(std::size_t min_wth) 25 : m_min_wth(min_wth) 26 { 27 } 28 29 template<typename I> 30 void print_header(I first, I last) 31 { 32 while (first != last) { 33 std::cout << std::format("{:^{}} | ", *first, m_min_wth); 34 first++; 35 } 36 std::cout << '\n'; 37 } 38 39 template<typename I> 40 void print_row(I first, I last, std::size_t precision) 41 { 42 std::cout << std::format("{:{}} | ", *first++, m_min_wth); 43 while (first != last) { 44 std::cout << std::format("{:{}.{}f} | ", *first, m_min_wth, precision); 45 first++; 46 } 47 std::cout << '\n'; 48 } 49 50 private: 51 std::size_t m_min_wth; 52 }; 53 54 inline auto dont_normalize(double x, double /* n */) 55 { 56 return x; 57 } 58 59 inline auto normalize_n(double x, double n) 60 { 61 return x / n; 62 } 63 64 inline auto normalize_nlogn(double x, double n) 65 { 66 return x / (n * (std::log(n) / std::numbers::ln2)); 67 } 68 69 inline auto normalize_nlogn1(double x, double n) 70 { 71 return x / (n * std::log(n) - n); 72 } 73 74 struct instrumented_base 75 { 76 BASED_ENUM( 77 op, 78 std::uint8_t, 79 n, 80 ctor_default, 81 ctor_value, 82 ctor_copy, 83 ctor_move, 84 asgn_copy, 85 asgn_move, 86 destructor, 87 equality, 88 comparison 89 ) 90 91 static constexpr std::array<const char*, op::size> names = { 92 "n", 93 "ctor_default", 94 "ctor_value", 95 "ctor_copy", 96 "ctor_move", 97 "asgn_copy", 98 "asgn_move", 99 "destructor", 100 "equality", 101 "comparison", 102 }; 103 104 static std::array<double, op::size> counts; 105 106 static constexpr auto op_num = op::size; 107 108 static void initialize(std::size_t size); 109 }; 110 111 template<typename T> 112 requires std::semiregular<T> 113 struct instrumented : instrumented_base 114 { 115 using value_type = T; 116 117 value_type value; 118 119 instrumented(const value_type& val) // NOLINT(*explicit*) 120 : value(std::move(val)) 121 { 122 ++counts[op::ctor_value]; 123 } 124 125 instrumented(value_type&& val) // NOLINT(*explicit*) 126 : value(std::move(val)) 127 { 128 ++counts[op::ctor_value]; 129 } 130 131 // Semiregular: 132 instrumented() { ++counts[op::ctor_default]; } 133 134 instrumented(const instrumented& val) 135 : value(val.value) 136 { 137 ++counts[op::ctor_copy]; 138 } 139 140 instrumented(instrumented&& val) noexcept 141 : value(std::move(val.value)) 142 { 143 ++counts[op::ctor_move]; 144 } 145 146 // self assign should be handled by the value_type 147 instrumented& operator=(const instrumented& val) // NOLINT(*cert-oop54-cpp*) 148 { 149 ++counts[op::asgn_copy]; 150 value = val.value; 151 return *this; 152 } 153 154 // self assign should be handled by the value_type 155 instrumented& operator=(instrumented&& val 156 ) noexcept // NOLINT(*cert-oop54-cpp*) 157 { 158 ++counts[op::asgn_move]; 159 value = std::move(val.value); 160 return *this; 161 } 162 163 ~instrumented() { ++counts[op::destructor]; } 164 165 // Regular 166 167 friend bool operator==(const instrumented& lhs, const instrumented& rhs) 168 { 169 ++counts[op::equality]; 170 return lhs.value == rhs.value; 171 } 172 173 friend bool operator!=(const instrumented& lhs, const instrumented& rhs) 174 { 175 return !(lhs == rhs); 176 } 177 178 // TotallyOrdered 179 180 friend bool operator<(const instrumented& lhs, const instrumented& rhs) 181 { 182 ++counts[op::comparison]; 183 return lhs.value < rhs.value; 184 } 185 186 friend bool operator>(const instrumented& lhs, const instrumented& rhs) 187 { 188 return rhs < lhs; 189 } 190 191 friend bool operator<=(const instrumented& lhs, const instrumented& rhs) 192 { 193 return !(rhs < lhs); 194 } 195 196 friend bool operator>=(const instrumented& lhs, const instrumented& rhs) 197 { 198 return !(lhs < rhs); 199 } 200 201 friend std::ostream& operator<<(std::ostream& ost, const instrumented& rhs) 202 { 203 return ost << rhs.value; 204 } 205 }; 206 207 template<typename D> 208 class registry 209 { 210 public: 211 static size_t count; 212 static D* head; 213 D* prev; 214 D* next; 215 216 registry(registry&&) = delete; 217 218 registry& operator=(const registry&) = delete; 219 registry& operator=(registry&&) = delete; 220 221 private: 222 registry() 223 : prev(nullptr) 224 , next(head) 225 { 226 head = static_cast<D*>(this); 227 ++count; 228 if (next) { 229 next->prev = head; 230 } 231 } 232 233 registry(const registry& /* reg */) 234 : registry() 235 { 236 } 237 238 ~registry() 239 { 240 --count; 241 if (prev != nullptr) { 242 prev->next = next; 243 } 244 if (next != nullptr) { 245 next->prev = prev; 246 } 247 if (head == this) { 248 head = next; 249 } 250 } 251 252 friend D; 253 }; 254 255 template<typename D> 256 size_t registry<D>::count(0); 257 258 template<typename D> 259 D* registry<D>::head(nullptr); 260 261 template<typename Function> 262 void count_operations( 263 size_t first, 264 size_t last, 265 Function fun, 266 double (*norm)(double, double) = dont_normalize 267 ) 268 { 269 using instrumented = based::instrumented<double>; 270 271 constexpr size_t cols = instrumented::op_num; 272 const size_t decimals((norm == dont_normalize) ? 0 : 2); 273 274 std::array<double, cols> values = {0}; 275 276 static constexpr int width = 12; 277 table tbl(width); 278 tbl.print_header( 279 std::begin(instrumented::names), std::end(instrumented::names) 280 ); 281 282 std::mt19937 rng(0); // NOLINT(*cert-msc32-c*, *cert-msc51-cpp*) 283 while (first <= last) { 284 std::vector<instrumented> vec(first); 285 std::iota(std::begin(vec), std::end(vec), 0.0); 286 std::shuffle(std::begin(vec), std::end(vec), rng); 287 288 instrumented::initialize(first); 289 fun(std::begin(vec), std::end(vec)); 290 291 const auto dbl = static_cast<double>(first); 292 293 values[0] = dbl; 294 for (size_t k = 1; k < cols; ++k) { 295 values[k] = norm(instrumented::counts[k], dbl); 296 } 297 298 tbl.print_row(std::begin(values), std::end(values), decimals); 299 300 first <<= 1U; 301 } 302 } 303 304 class timer 305 { 306 public: 307 using clock_t = std::chrono::high_resolution_clock; 308 using duration_t = std::chrono::microseconds; 309 310 timer() 311 : m_startp(clock_t::now()) 312 { 313 } 314 315 timer(const timer&) = delete; 316 timer(timer&&) = delete; 317 timer& operator=(const timer&) = delete; 318 timer& operator=(timer&&) = delete; 319 320 ~timer() 321 { 322 stop(); 323 std::cout << std::flush; 324 } 325 326 void stop() 327 { 328 static const auto count = [](const auto& time) 329 { 330 return std::chrono::time_point_cast<duration_t>(time) 331 .time_since_epoch() 332 .count(); 333 }; 334 335 const auto endp = clock_t::now(); 336 337 const auto start = count(m_startp); 338 const auto end = count(endp); 339 340 const auto duration = end - start; 341 const auto msec = static_cast<double>(duration) * 0.001; 342 343 std::cout << std::format("{}us ({}ms)\n", duration, msec); 344 } 345 346 private: 347 std::chrono::time_point<clock_t> m_startp; 348 }; 349 350 } // namespace based