Linux C ++:如何分析由于缓存未命中而浪费的时间?

时间:2010-03-21 11:18:13

标签: c++ linux caching profiling

我知道我可以使用gprof来对我的代码进行基准测试。

但是,我有这个问题 - 我有一个智能指针,它具有额外的间接级别(将其视为代理对象)。

结果,我有了这个额外的层,它几乎影响了所有的功能,并且带有缓存功能。

有没有办法衡量因缓存未命中而导致CPU浪费的时间?

谢谢!

9 个答案:

答案 0 :(得分:18)

您可以尝试cachegrind,它是前端kcachegrind。

答案 1 :(得分:11)

您可以找到一个访问CPU性能计数器的工具。每个核心中可能存在一个寄存器,其中包含L1,L2等未命中数。或者,Cachegrind执行逐周期模拟。

但是,我认为这不会有洞察力。您的代理对象可能是由他们自己的方法修改的。 传统的分析器会告诉您这些方法需要多长时间。没有配置文件工具可以告诉您如果没有缓存污染源,性能会如何提高。这是一个减少程序工作集的大小和结构的问题,这不容易推断。

您可能感兴趣的快速Google搜索boost::intrusive_ptr。它似乎不支持像weak_ptr这样的东西,但转换你的程序可能很简单,然后你肯定会知道非侵入式引用的成本。

答案 2 :(得分:10)

Linux支持从2.6.31开始使用perf。这允许您执行以下操作:

  • 使用-g编译代码以包含调试信息
  • 运行您的代码,例如使用最后一级缓存未命中计数器:perf record -e LLC-loads,LLC-load-misses yourExecutable
  • 运行perf report
    • 确认初始消息后,选择LLC-load-misses行,
    • 然后,例如第一个功能和
    • 然后annotate。您应该看到行(在汇编代码中,由原始源代码包围)和一个数字,表示发生缓存未命中的行的最后一级缓存未命中的部分。

答案 3 :(得分:5)

这取决于您使用的操作系统和CPU。例如。对于Mac OS X和x86或ppc,Shark将执行缓存未命中分析。 Linux上的Zoom同上。

答案 4 :(得分:5)

如果你正在运行AMD处理器,你可以得到CodeAnalyst,显然像啤酒一样免费。

答案 5 :(得分:5)

继续沿着@ Mike_Dunlavey的回答:

首先,使用您最喜欢的工具获取基于时间的配置文件:VTune或PTU或OProf。

然后,获取缓存未命中配置文件。 L1缓存未命中,或L2缓存未命中,或......

即。第一个配置文件将“花费的时间”与每个程序计数器相关联。 第二个将“缓存未命中数”值与每个程序计数器相关联。

注意:我经常“减少”数据,按功能加总,或者(如果我有技术)循环。或者说是64字节的二进制数。比较单个程序计数器通常没有用,因为性能计数器是模糊的 - 您看到报告缓存未命中的位置通常是与实际发生位置不同的几个指令。

好的,现在将这两个配置文件绘制成比较它们。以下是一些我觉得有用的图表:

“冰山”图表:X轴是PC,正Y轴是时间,负Y访问是缓存未命中。查找上下两个位置。

(“Interleaved”图表也很有用:同样的想法,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)+大于say,4 * sizeof(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-&gt; handle-&gt; ptr 可能是一个很大的性能问题。或者甚至是三重间接,这是&gt; handle-&gt; ptr-&gt;字段。至少,在具有5个周期L1高速缓存命中的机器上,每个这个 - > handle-&gt; ptr-&gt;字段将花费10个周期。并且比单指针追逐更难重叠。但是,更糟糕的是,如果每个都是L1高速缓存未命中,即使它只是到L2的20个周期......好吧,隐藏2 * 20 = 40个周期的高速缓存未命中延迟比单个L1未命中要困难得多。

一般来说,避免智能指针中的间接级别是一个很好的建议。所有智能指针都指向一个句柄,而不是指向一个句柄,它本身指向该对象,你可以通过指向对象和句柄来使智能指针更大。 (这不再是通常所说的句柄,而更像是一个信息对象。)

E.g。

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>;
};

无论如何 - 时间档案是你最好的朋友。


哦,是的 - 英特尔EMON硬件还可以告诉您在PC上等待的周期数。这可以区分大量L1未命中和少量L2未命中。

答案 6 :(得分:2)

我的建议是使用英特尔的PTU(性能调优实用程序)。

此实用程序是VTune的直接后代,提供可用的最佳可用采样分析器。您将能够跟踪CPU花费的时间或浪费时间(借助可用的硬件事件),并且不会降低应用程序速度或减少配置文件的干扰。 当然,您将能够收集您正在寻找的所有缓存行未命中事件。

答案 7 :(得分:2)

CPU性能基于计数器的分析的另一个工具是oprofile。您可以使用kcachegrind查看其结果。

答案 8 :(得分:2)

这是一种general answer

例如,如果您的程序花费了50%的时间用于缓存未命中,那么50%的时间暂停它时,程序计数器将位于等待内存提取的确切位置导致缓存未命中的原因。