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

  1. Call esp_cache_cnt_start() to clear and enable all counters.

  2. Run the code to be measured.

  3. Call esp_cache_cnt_stop() to disable the counters, so that reading out and reporting the results is not counted as well.

  4. Call esp_cache_cnt_dump() to print a table of all counter values, or read the values of individual units with esp_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

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.

esp_err_t esp_cache_cnt_dump(FILE *out)

Print a table with the current values of all cache access counters.

Parameters:

out -- Output stream; if NULL, print to stdout

Returns:

  • ESP_OK on success

  • ESP_ERR_NOT_SUPPORTED if the target has no cache access counters

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

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

Was this page helpful?