Cache Access Counters
Introduction
ESP32-C61 has hardware counters attached to the cache request buses. They record the number of completed cache accesses, miss stall events, requester conflicts, and cache lines transferred to and from the next level of the memory hierarchy. These counters can be used to measure the cache hit/miss behavior of a piece of code, for example to choose the placement of data in memory, or to find out why some code runs slower than expected.
Counter Units
The set of counters differs between chips: the number of cache levels, the number of request buses per cache, and which counters exist per bus all vary. Instead of a fixed list of caches, the API exposes a chip-defined list of counter units. Each unit is one set of counters observing one traffic stream, for example instruction fetches from core 0 into the L1 cache. Applications enumerate the units at runtime using esp_cache_cnt_num_units() and esp_cache_cnt_get_unit_info(), so they keep working when a new chip adds or removes units.
Usage
Call
esp_cache_cnt_start()to clear and enable all counters.Run the code to be measured.
Call
esp_cache_cnt_stop()to disable the counters, so that reading out and reporting the results is not counted as well.Call
esp_cache_cnt_dump()to print a table of all counter values, or read the values of individual units withesp_cache_cnt_get().
esp_cache_cnt_clear() resets the counters without changing whether they are running, which is useful when the counters have to stay enabled across measurement phases.
Counter Semantics
For each unit, esp_cache_cnt_get() returns:
accesses: the number of completed accesses. The hardware "hit" counter increments once for every access that completes, whether or not the access had to wait for a line fill first, so it is reported as the total access count.stall_events: incremented repeatedly while an access is stalled on a miss. This value grows with the total miss latency, not with the number of missed accesses, so it is only useful as a relative measure.conflicts: the number of conflicts between requesters on the cache.line_fills: the number of lines fetched from the next level of the memory hierarchy. This is the true miss count.writebacks: the number of lines written back to the next level. Only present for data traffic on chips with a write-back cache.
The miss ratio of a unit is therefore line_fills / accesses, available as esp_cache_cnt_miss_ratio(). Not every counter exists for every unit; a field of esp_cache_cnt_data_t is only meaningful if the corresponding bit of the valid_mask member is set.
Application Examples
system/cache_counters runs a read workload over working sets of different sizes and placements, and prints the counter values after each run, showing how the working set size determines which level of the memory hierarchy serves the accesses.
API Reference
Header File
This header file can be included with:
#include "esp_cache_cnt.h"
Functions
-
size_t esp_cache_cnt_num_units(void)
Number of counter units on this chip.
- Returns:
Number of units; 0 if the chip has no cache access counters.
-
esp_err_t esp_cache_cnt_get_unit_info(size_t unit, esp_cache_cnt_unit_info_t *out)
Get the description of a counter unit.
- Parameters:
unit -- Unit index, 0 to esp_cache_cnt_num_units() - 1
out -- [out] Unit description
- Returns:
ESP_OK on success
ESP_ERR_INVALID_ARG if unit is out of range or out is NULL
-
esp_err_t esp_cache_cnt_start(void)
Clear and enable all cache access counters.
- Returns:
ESP_OK on success
ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
-
esp_err_t esp_cache_cnt_stop(void)
Disable all cache access counters. Counter values are retained.
- Returns:
ESP_OK on success
ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
-
esp_err_t esp_cache_cnt_clear(void)
Reset all cache access counters to zero. Counting state is not changed.
- Returns:
ESP_OK on success
ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
-
esp_err_t esp_cache_cnt_get(size_t unit, esp_cache_cnt_data_t *out)
Read the current counter values for the given unit.
- Parameters:
unit -- Unit index, 0 to esp_cache_cnt_num_units() - 1
out -- [out] Counter values
- Returns:
ESP_OK on success
ESP_ERR_INVALID_ARG if unit is out of range or out is NULL
ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
-
float esp_cache_cnt_miss_ratio(const esp_cache_cnt_data_t *data)
Miss ratio (0.0 to 1.0) computed from a set of counter values.
- Parameters:
data -- Counter values obtained from esp_cache_cnt_get()
- Returns:
Miss ratio; 0.0 if the unit does not provide the counters needed to compute it.
Structures
-
struct esp_cache_cnt_unit_info_t
Description of one counter unit.
Public Members
-
const char *name
Short human-readable name, e.g. "l1-icache-core0"
-
uint8_t cache_level
Cache level the counters belong to, counting from the CPU. On most chips there is a single level (the flash/PSRAM cache); on the ESP32-P4, level 1 is the L1 cache in front of internal memory and level 2 is the flash/PSRAM cache.
-
cache_profile_traffic_t traffic_type
Kind of traffic observed
-
int8_t core_id
Core the traffic originates from, or -1 if unknown/mixed
-
const char *name
-
struct esp_cache_cnt_data_t
Counter values for one unit.
Note that the hardware "hit" and "miss" counters do not directly hold the number of hit and missed accesses:
accesses: the hit counter increments once for every access that completes, whether or not it had to wait for a line fill first.
stall_events: the miss counter increments repeatedly while an access is stalled on a miss, so it grows roughly with the total miss latency. This is only useful as a relative measure.
line_fills: the next-level read counter increments once per line fetched from the next level, so it is the true miss count.
The miss ratio of a cache is therefore line_fills / accesses.
Public Members
-
uint32_t valid_mask
Bitwise OR of ESP_CACHE_CNT_VALID_* flags for the fields below
-
uint32_t accesses
Completed accesses (hardware hit counter)
-
uint32_t stall_events
Miss stall events; grows with total miss latency, NOT the number of missed accesses
-
uint32_t conflicts
Conflicts between requesters on this cache
-
uint32_t line_fills
Lines fetched from the next level (true miss count)
-
uint32_t writebacks
Lines written back to the next level. Only present for data traffic on chips with a write-back cache (PSRAM support, see SOC_CACHE_WRITEBACK_SUPPORTED).
Macros
-
ESP_CACHE_CNT_VALID_ACCESSES
-
ESP_CACHE_CNT_VALID_STALL_EVENTS
-
ESP_CACHE_CNT_VALID_CONFLICTS
-
ESP_CACHE_CNT_VALID_LINE_FILLS
-
ESP_CACHE_CNT_VALID_WRITEBACKS