| // Copyright 2024 Google LLC | |
| // SPDX-License-Identifier: Apache-2.0 | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #ifndef HIGHWAY_HWY_PERF_COUNTERS_H_ | |
| #define HIGHWAY_HWY_PERF_COUNTERS_H_ | |
| // Reads OS/CPU performance counters. | |
| #include <stddef.h> | |
| #include "third_party/highway/hwy/base.h" // HWY_ABORT | |
| #include "third_party/highway/hwy/bit_set.h" | |
| namespace hwy { | |
| namespace platform { | |
| // Avoid padding in case callers such as profiler.h store many instances. | |
| #pragma pack(push, 1) | |
| // Provides access to CPU/OS performance counters. Each instance has space for | |
| // multiple counter values; which counters these are may change in future. | |
| // Although counters are per-CPU, Linux accesses them via a syscall, hence we | |
| // use the monostate pattern to avoid callers having to pass around a pointer. | |
| // Note that this is not thread-safe, so the static member functions should only | |
| // be called from the main thread. | |
| class PerfCounters { | |
| public: | |
| // Chosen such that this class occupies one or two cache lines. | |
| static constexpr size_t kCapacity = 14; | |
| // Bit indices used to identify counters. The ordering is arbitrary. Some of | |
| // these counters may be 'removed' in the sense of not being visited by | |
| // `Foreach`, but their enumerators will remain. New counters may be appended. | |
| enum Counter { | |
| kRefCycles = 0, | |
| kInstructions, | |
| kBranches, | |
| kBranchMispredicts, | |
| kBusCycles, | |
| kCacheRefs, | |
| kCacheMisses, | |
| kL3Loads, | |
| kL3Stores, | |
| kPageFaults, // SW | |
| kMigrations // SW | |
| }; // BitSet64 requires these values to be less than 64. | |
| // Strings for user-facing messages, not used in the implementation. | |
| static inline const char* Name(Counter c) { | |
| switch (c) { | |
| case kRefCycles: | |
| return "ref_cycles"; | |
| case kInstructions: | |
| return "instructions"; | |
| case kBranches: | |
| return "branches"; | |
| case kBranchMispredicts: | |
| return "branch_mispredicts"; | |
| case kBusCycles: | |
| return "bus_cycles"; | |
| case kCacheRefs: | |
| return "cache_refs"; | |
| case kCacheMisses: | |
| return "cache_misses"; | |
| case kL3Loads: | |
| return "l3_load"; | |
| case kL3Stores: | |
| return "l3_store"; | |
| case kPageFaults: | |
| return "page_fault"; | |
| case kMigrations: | |
| return "migration"; | |
| default: | |
| HWY_ABORT("Bug: unknown counter %d", c); | |
| } | |
| } | |
| // Returns false if counters are unavailable. Must be called at least once | |
| // before `StartAll`; it is separate to reduce the overhead of repeatedly | |
| // stopping/starting counters. | |
| HWY_DLLEXPORT static bool Init(); | |
| // Returns false if counters are unavailable, otherwise starts them. Note that | |
| // they default to stopped. Unless this is called, the values read may be 0. | |
| HWY_DLLEXPORT static bool StartAll(); | |
| // Stops and zeros all counters. This is not necessary if users subtract the | |
| // previous counter values, but can increase precision because floating-point | |
| // has more precision near zero. | |
| HWY_DLLEXPORT static void StopAllAndReset(); | |
| // Reads the current (extrapolated, in case of multiplexing) counter values. | |
| HWY_DLLEXPORT PerfCounters(); | |
| // Returns whether any counters were successfully read. | |
| bool AnyValid() const { return valid_.Any(); } | |
| // Returns whether the given counter was successfully read. | |
| bool IsValid(Counter c) const { | |
| const size_t bit_idx = static_cast<size_t>(c); | |
| return valid_.Get(bit_idx); | |
| } | |
| // Returns the maximum extrapolation factor for any counter, which is the | |
| // total time between `StartAll` and now or the last `StopAllAndReset`, | |
| // divided by the time that the counter was actually running. This | |
| // approximates the number of counter groups that the CPU multiplexes onto the | |
| // actual counter hardware. It is only meaningful if AnyValid(). | |
| double MaxExtrapolate() const { return max_extrapolate_; } | |
| // Returns the value of the given counter, or zero if it is not valid. | |
| double Get(Counter c) const { | |
| return IsValid(c) ? values_[IndexForCounter(c)] : 0.0; | |
| } | |
| // For each valid counter in increasing numerical order, calls `visitor` with | |
| // the value and `Counter`. | |
| template <class Visitor> | |
| void Foreach(const Visitor& visitor) { | |
| valid_.Foreach([&](size_t bit_idx) { | |
| const Counter c = static_cast<Counter>(bit_idx); | |
| visitor(values_[IndexForCounter(c)], c); | |
| }); | |
| } | |
| private: | |
| // Index within `values_` for a given counter. | |
| HWY_DLLEXPORT static size_t IndexForCounter(Counter c); | |
| BitSet64 valid_; | |
| double max_extrapolate_; | |
| // Floating-point because these are extrapolated (multiplexing). It would be | |
| // nice for this to fit in one cache line to reduce the cost of reading | |
| // counters in profiler.h, but some of the values are too large for float and | |
| // we want more than 8 counters. Ensure all values are sums, not ratios, so | |
| // that profiler.h can add/subtract them. These are contiguous in memory, in | |
| // the order that counters were initialized. | |
| double values_[kCapacity]; | |
| }; | |
| #pragma pack(pop) | |
| } // namespace platform | |
| } // namespace hwy | |
| #endif // HIGHWAY_HWY_PERF_COUNTERS_H_ |