缓存访问计数器

[English]

简介

ESP32-C61 的缓存请求总线上带有硬件计数器,用于记录已完成的缓存访问次数、未命中停顿事件、请求方冲突次数,以及与下一级存储之间传输的缓存行数量。借助这些计数器,可以测量某段代码的缓存命中/未命中情况,例如用于选择数据在内存中的放置位置,或分析某段代码运行速度不及预期的原因。

计数器单元

不同芯片提供的计数器有所不同:缓存层级数、每个缓存的请求总线数量,以及每条总线上存在哪些计数器都可能不同。因此,API 没有定义固定的缓存列表,而是提供由芯片定义的计数器 单元 列表。每个单元是观察同一数据流的一组计数器,例如 CPU0 向 L1 缓存发出的取指请求。应用程序可在运行时通过 esp_cache_cnt_num_units()esp_cache_cnt_get_unit_info() 枚举这些单元,因此当新芯片增加或减少单元时,应用程序无需修改。

使用方法

  1. 调用 esp_cache_cnt_start(),清零并使能所有计数器。

  2. 运行需要测量的代码。

  3. 调用 esp_cache_cnt_stop() 停止计数,这样读取和打印结果本身不会被计入。

  4. 调用 esp_cache_cnt_dump() 打印所有计数器数值的表格,或通过 esp_cache_cnt_get() 读取单个单元的计数值。

esp_cache_cnt_clear() 在不改变计数使能状态的情况下将计数器清零,适用于需要在多个测量阶段之间保持计数器使能的场景。

计数器含义

对于每个单元,esp_cache_cnt_get() 返回以下计数值:

  • accesses:已完成的访问次数。硬件的“命中”计数器在每次访问完成时加一,无论该访问是否先等待了缓存行填充,因此这里将其报告为总访问次数。

  • stall_events:访问因未命中而停顿期间会反复递增。该数值随未命中总延迟增长,而不是未命中的访问次数,因此只能用作相对指标。

  • conflicts:该缓存上请求方之间的冲突次数。

  • line_fills:从下一级存储取回的缓存行数量。这是真正的未命中次数。

  • writebacks:写回到下一级存储的缓存行数量。仅在具有写回型缓存的芯片上对数据流量提供。

因此,一个单元的未命中率为 line_fills / accesses,可通过 esp_cache_cnt_miss_ratio() 计算。并非每个单元都具有全部计数器;只有当 esp_cache_cnt_data_tvalid_mask 成员中相应位被置位时,对应字段才有意义。

应用示例

  • system/cache_counters 对不同大小和位置的工作集运行读取负载,并在每次运行后打印计数器数值,展示工作集大小如何决定由存储层级中的哪一级来响应访问。

API 参考

Header File

Functions

size_t esp_cache_cnt_num_units(void)

Number of counter units on this chip.

返回:

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.

参数:
  • unit -- Unit index, 0 to esp_cache_cnt_num_units() - 1

  • out -- [out] Unit description

返回:

  • 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.

返回:

  • 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.

返回:

  • 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.

返回:

  • 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.

参数:
  • unit -- Unit index, 0 to esp_cache_cnt_num_units() - 1

  • out -- [out] Counter values

返回:

  • 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.

参数:

data -- Counter values obtained from esp_cache_cnt_get()

返回:

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.

参数:

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

返回:

  • 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

此文档对您有帮助吗?