Linux C++:如何分析由于缓存未命中而浪费的时间?
- 2024-11-07 08:55:00
- admin 原创
- 30
问题描述:
我知道我可以使用 gprof 来对我的代码进行基准测试。
但是,我遇到了这个问题——我有一个具有额外间接级别的智能指针(可以将其视为代理对象)。
结果,我有了这个额外的层,它影响了几乎所有的功能,并且影响了缓存。
有没有办法测量我的 CPU 因缓存未命中而浪费的时间?
解决方案 1:
Linux 从 2.6.31 开始支持perf
。这允许您执行以下操作:
使用 -g 编译代码以包含调试信息
运行你的代码,例如使用最后一级缓存未命中计数器:
perf record -e LLC-loads,LLC-load-misses yourExecutable
跑步
perf report
确认初始消息后,选择该
LLC-load-misses
行,然后例如第一个函数和
然后
annotate
。您应该看到这些行(以汇编代码表示,被原始源代码包围)和一个数字,该数字表示发生缓存未命中的行中最后一级缓存未命中的比例。
解决方案 2:
您可以尝试cachegrind及其前端 kcachegrind。
解决方案 3:
您可以找到一个访问 CPU 性能计数器的工具。每个核心中可能都有一个寄存器,用于计算 L1、L2 等未命中次数。或者,Cachegrind 会执行逐周期模拟。
但是,我认为这没什么用。您的代理对象可能被它们自己的方法修改了。传统的分析器会告诉您这些方法花费了多少时间。没有分析工具会告诉您如果没有缓存污染源,性能会如何提高。这是减少程序工作集的大小和结构的问题,这很难推断。
快速 Google 搜索结果boost::intrusive_ptr
可能会让您感兴趣。它似乎不支持类似的东西weak_ptr
,但转换您的程序可能很简单,然后您就会确切知道非侵入式引用计数的成本。
解决方案 4:
继续@Mike_Dunlavey 的回答:
首先,使用您最喜欢的工具获取基于时间的配置文件:VTune 或 PTU 或 OProf。
然后,获取缓存未命中概况。L1 缓存未命中,或 L2 缓存未命中,或...
即第一个配置文件将“花费的时间”与每个程序计数器关联起来。第二个配置文件将“缓存未命中次数”值与每个程序计数器关联起来。
注意:我经常“减少”数据,按函数或(如果我有技术的话)按循环进行汇总。或者按 64 字节的箱进行汇总。比较单个程序计数器通常没有用,因为性能计数器很模糊 - 您看到报告缓存未命中的位置通常与实际发生的位置有几条指令不同。
好的,现在绘制这两个配置文件的图表来比较它们。以下是一些我认为有用的图表:
“冰山”图:X 轴表示 PC,正 Y 轴表示时间,负 Y 轴表示缓存未命中。 寻找同时上升和下降的位置。
(“交错”图表也很有用:同样的想法,X 轴是 PC,在 Y 轴上绘制时间和缓存未命中,但使用不同颜色的窄垂直线,通常是红色和蓝色。花费大量时间和缓存未命中的地方将有精细交错的红线和蓝线,几乎看起来是紫色。这延伸到 L2 和 L3 缓存未命中,都在同一个图表上。顺便说一句,您可能希望“规范化”数字,要么是总时间或缓存未命中的百分比,或者更好的是,最大数据点时间或缓存未命中的百分比。如果您弄错了比例,您将什么也看不到。)
XY 图表:对于每个采样箱(PC、函数、循环等),绘制一个点,其X 坐标为归一化时间,Y 坐标为归一化缓存未命中数。如果您在右上角获得大量数据点 - 大百分比时间和大百分比缓存未命中数 - 那就是有趣的证据。或者,忘记点数 - 如果上角所有百分比的总和很大...
请注意,不幸的是,您经常必须自己进行这些分析。我上次检查时发现 VTune 不会为您执行此操作。我曾使用过 gnuplot 和 Excel。(警告:Excel 在超过 64,000 个数据点时会失效。)
更多建议:
如果您的智能指针是内联的,您可能会得到到处都是的计数。在理想情况下,您将能够将 PC 追溯到源代码的原始行。在这种情况下,您可能希望稍微推迟减少:查看所有单个 PC;将它们映射回源代码行;然后将它们映射到原始函数中。许多编译器(例如 GCC)都有允许您执行此操作的符号表选项。
顺便说一句,我怀疑你的问题不是智能指针导致的缓存抖动。除非你到处都在做 smart_ptr<int>。如果你正在做 smart_ptr<Obj>,并且 sizeof(Obj) + 大于 4sizeof(Obj)(并且如果 smart_ptr 本身不是很大),那么它就没那么多。
更可能的是,智能指针的额外间接级别导致了您的问题。
巧合的是,我在午餐时和一个家伙聊天,他有一个引用计数智能指针,它使用句柄,即间接级别,类似于
template<typename T> class refcntptr {
refcnt_handle<T> handle;
public:
refcntptr(T*obj) {
this->handle = new refcnt_handle<T>();
this->handle->ptr = obj;
this->handle->count = 1;
}
};
template<typename T> class refcnt_handle {
T* ptr;
int count;
friend refcnt_ptr<T>;
};
(我不会这样编码,但它有助于说明。)
双重间接this->handle->ptr可能会带来很大的性能问题。或者甚至是三重间接,this->handle->ptr->field。至少,在具有 5 个周期 L1 缓存命中的机器上,每个 this->handle->ptr->field 都需要 10 个周期。而且重叠起来比单个指针追逐要困难得多。但更糟糕的是,如果每个都是 L1 缓存未命中,即使到 L2 只有 20 个周期……那么,隐藏 2*20=40 个周期的缓存未命中延迟要比隐藏单个 L1 未命中困难得多。
一般来说,避免智能指针中的间接层级是个好建议。不要指向所有智能指针都指向的句柄,而句柄本身又指向对象,而是让智能指针既指向对象又指向句柄,从而使智能指针变得更大。(这样就不再是通常所说的句柄,而更像是一个信息对象。)
例如
template<typename T> class refcntptr {
refcnt_info<T> info;
T* ptr;
public:
refcntptr(T*obj) {
this->ptr = obj;
this->info = new refcnt_handle<T>();
this->info->count = 1;
}
};
template<typename T> class refcnt_info {
T* ptr; // perhaps not necessary, but useful.
int count;
friend refcnt_ptr<T>;
};
无论如何,时间概况是你最好的朋友。
哦,是的 - Intel EMON 硬件还可以告诉您在 PC 上等待了多少个周期。这可以区分大量的 L1 未命中和少量的 L2 未命中。
解决方案 5:
这取决于您使用的操作系统和 CPU。例如,对于 Mac OS X 和 x86 或 ppc,Shark将进行缓存未命中分析。Linux 上的Zoom也是如此。
解决方案 6:
如果您运行的是 AMD 处理器,您可以获得CodeAnalyst,显然就像啤酒一样免费。
解决方案 7:
我的建议是使用英特尔的PTU(性能调整实用程序)。
此实用程序是 VTune 的直接后代,提供最佳的采样分析器。您将能够跟踪 CPU 在哪里花费或浪费时间(借助可用的硬件事件),并且不会减慢您的应用程序或干扰分析。当然,您将能够收集您正在寻找的所有缓存行未命中事件。
解决方案 8:
另一个基于 CPU 性能计数器的分析工具是oprofile。您可以使用 kcachegrind 查看其结果。
解决方案 9:
这是一种一般性的答案。
例如,如果您的程序将 50% 的时间花在了缓存未命中上,那么当您暂停程序时,程序计数器的 50% 的时间将位于等待导致缓存未命中的内存提取的确切位置。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件