You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

std vector数据加载累加时缓存缺失及perf相关技术咨询

缓存缺失相关问题解答

问题背景

Perf报告显示,一个从磁盘加载二进制Blob并累加元素的简单C++应用存在约3×10⁷次缓存缺失。程序代码如下:

#include <fstream>
#include <iostream>
#include <numeric>
#include <vector>

std::vector<float> read_blob(const std::string& file_path, int sz) {
    std::vector<float> data(sz);
    std::fstream file(file_path, std::ios::binary | std::ios::in);
    if (!file.is_open()) {
        throw std::runtime_error{"Cannot open buffer from disk"};
    }
    file.read((char*)data.data(), sizeof(float) * sz);
    file.close();
    return data;
}

int main() {
    constexpr size_t sz{311040000};
    const std::string path{"/tmp/blob.bin"};
    std::vector<float> data = read_blob(path, sz);
    float sum =
        std::accumulate(data.begin(), data.end(), 0.0F) / (float)data.size();
    std::cout << "the sum is " << sum << std::endl;
}

程序使用-O3 -march=native编译。进一步分析发现缓存缺失的分布如下:

模块缓存缺失数量
brnf_frag_data_storage ([br_netfilter])2.058×10⁷ (60%)
Main1.325×10⁷ (40%)
Total3.838×10⁷

1. main函数执行前发生了什么?

  • 程序启动阶段,操作系统会完成进程初始化流程:将可执行文件加载到内存、解析动态链接库(若为动态编译)、初始化进程环境变量、分配栈空间等。
  • 统计到的brnf_frag_data_storage属于内核br_netfilter(网桥网络过滤器)模块,这部分缓存缺失和用户态程序无关,是内核处理网络分片存储时产生的——大概率是系统中其他网络活动触发了该模块操作,恰好被perf统计到(perf默认会统计进程全生命周期内的所有事件,包括内核态相关操作)。
  • 此外,C++全局对象的构造、标准库的初始化(比如std::cout的初始化)也会在main执行前完成,但这部分产生的缓存缺失量级远低于内核模块的统计结果。

2. 能否配置perf仅统计main函数启动后的事件?

可以,有几种可行方案:

  • 符号过滤:使用perf record -e cache-misses --filter 'filter main:*' ./your_program,直接指定只统计main函数及后续调用栈的事件(需确保perf支持符号过滤)。
  • 手动标记起点:在main开头插入空函数(如void perf_mark() {}),编译后用perf record -e cache-misses -g ./your_program,后续在perf report中仅分析perf_mark之后的调用栈事件。
  • 忽略内核态事件:如果只关心用户态缓存缺失,可执行perf record -e cache-misses --user-only ./your_program,这样会排除内核模块(如br_netfilter)产生的事件。
  • 探针触发统计:用perf probe在main入口创建探针,再统计探针触发后的事件:
    perf probe -x ./your_program main
    perf record -e cache-misses -e probe:main --filter 'probe:main' ./your_program
    

3. 鉴于vector长度,每约31个元素就出现一次缓存缺失,为何频率如此之高?

先明确数据规模:vector包含311040000个float元素,每个float占4字节,总大小为311040000 * 4 = 1.244GB,远超过常见CPU的L3缓存容量(通常为几MB到几十MB),核心原因如下:

  • 缓存层级的累计缺失:perf统计的是L1、L2、L3所有层级的缓存缺失总和,并非仅L1缺失。比如数据从L2加载到L1失败、从L3加载到L2失败都会被计入,累加后会让平均缺失间隔变小。
  • 数据加载与遍历的时序冲突:磁盘读取数据到内存时是按页(通常4KB)加载,理论上每1024个float元素触发一次页缺失,但std::accumulate可能在数据刚写入内存就开始遍历,CPU预取器还未将后续数据加载到缓存,导致频繁缺失。
  • 内存压力导致缓存淘汰:如果系统内存紧张,刚加载到缓存的页可能被其他进程或内核操作挤出缓存,触发二次缺失,进一步提升缺失频率。

内容的提问来源于stack exchange,提问作者fabian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 08:14:51