Linux的C + +:如何configuration时间浪费由于caching未命中?

我知道我可以使用gprof来testing我的代码。

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

结果,我有这个额外的层,影响几乎所有的function,并与caching螺丝。

有没有办法测量我的CPU由于caching未命中而浪费的时间?

谢谢!

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

您可以find一个访问CPU性能计数器的工具。 在每个核心中可能有一个loggingL1,L2等缺失的寄存器。 Cachegrind也可以执行循环模拟。

但是,我不认为这将是有见地的。 你的代理对象大概是用自己的方法修改的。 传统的分析器会告诉你这些方法需要多less时间。 没有configuration文件工具会告诉你如果没有caching污染的源头,性能会如何提高。 这是减less程序工作集的大小和结构的问题,这是不容易推断的。

快速谷歌search出现了boost::intrusive_ptr ,这可能会让你感兴趣。 它似乎不支持像weak_ptr这样的东西,但是转换你的程序可能是微不足道的,那么你肯定会知道非侵入性的ref计数的代价。

Linux支持从2.6.31开始的perf 。 这使您可以执行以下操作:

  • 使用-g编译您的代码以包含debugging信息
  • 运行你的代码,例如使用最后一级caching未命中计数器: perf record -e LLC-loads,LLC-load-misses yourExecutable
  • 运行性能perf report
    • 确认初始信息后,selectLLC-load-misses线,
    • 然后例如第一个function和
    • 然后annotate 。 您应该看到(在汇编代码中,由原始源代码包围的)行,以及一个数字,指示发生caching未命中行的最后一级caching的哪一部分未命中。

这取决于你正在使用的操作系统和CPU。 例如,对于Mac OS X和x86或ppc, Shark将执行caching未命中分析。 在Linux上放大缩小 。

如果您使用的是AMD处理器,则可以获得CodeAnalyst ,显然免费,就像啤酒一样。

继续@ Mike_Dunlavey的回答:

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

然后,获取一个caching缺失configuration文件。 一级caching未命中,或二级caching未命中,或…

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

注意:我经常“减less”数据,按function进行总结,或者(如果有技术的话)循环。 或者通过比如说64字节的分箱。 比较个别的程序计数器通常是没有用的,因为性能计数器是模糊的 – 你所看到的caching未命中的地方往往是几条不同于实际发生的指令。

好的,现在就绘制这两个configuration文件来比较它们。 以下是我认为有用的一些图表:

“冰山”图表:X轴是PC,正Y轴是时间,负Y访问是caching未命中。 寻找上下的地方。

(“Interleaved”图表也是有用的:同样的想法,X轴是PC,在Y轴上绘制时间和caching错误,但是用不同颜色的垂直线条,通常是红色和蓝色。花费的错误将会有细微的交错的红色和蓝色的线条,几乎看起来是紫色的,这可以延伸到二级和三级caching未命中,所有这些都在同一个图表上。顺便说一句,你可能想要“正常化”数字,时间或者caching丢失,或者甚至更好,最大数据时间点或caching未命中的年龄,如果你的规模不对,你什么也看不到。

XY图表 :对于每个采样点(PC,或函数,或循环或…)绘制一个点,其X坐标是标准化时间,其Y坐标是标准化caching未命中 。 如果你在右上angular得到大量的数据点 – 大的年龄时间和大%的年龄caching未命中 – 这是有趣的证据。 或者忘记点数 – 如果上angular所有百分比的总和很大

注意,不幸的是,你经常不得不自己滚动这些分析。 最后我检查了VTune没有为你做。 我用过gnuplot和Excel。 (警告:Excel超过64000个数据点。


更多的build议:

如果你的智能指针被内联,你可能会得到所有的地方。 在理想的世界中,您将能够追溯PC到源代码的原始行。 在这种情况下,您可能想稍微延迟一下:查看所有个人电脑; 将它们映射回源代码行; 然后将这些映射到原始函数中。 许多编译器,例如GCC,都有符号表选项,允许你这样做。

顺便说一句,我怀疑你的问题不是智能指针导致caching颠簸。 除非你在做smart_ptr <int>到处都是。 如果你正在做smart_ptr <Obj>,并且sizeof(Obj)+大于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-> handle-> ptr可能是一个很大的性能问题。 甚至三重间接,this-> handle-> ptr->字段。 至less,在具有5个周期L1高速caching命中的机器上,每个this-> handle-> ptr->字段将花费10个周期。 而且比单个指针追踪要难得多。 但是,更糟的是,如果每个都是L1高速caching未命中,即使只有20个周期到L2 …好,隐藏2×20 = 40个高速caching未命中延迟周期要比单个L1未命中要困难得多。

一般来说,在智能指针中避免间接级别是一个很好的build议。 所有智能指针指向的是指向对象的指针,而不是指向句柄,而是指向指向对象的所有智能指针,您可以通过指向对象以及句柄来使智能指针变大。 (这不再是通常所说的句柄,而更像是一个信息对象。)

例如

 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上等待了多less个周期。 这可以区分大量的L1缺失和less量的L2缺失。

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

此实用程序是VTune的直接后代,并提供可用的最佳可用采样分析器。 您将能够跟踪CPU花费的时间或浪费时间(借助于可用的硬件事件),而不会减慢应用程序的运行速度或扰乱configuration文件。 当然,您可以收集所有正在查找的caching行未命中事件。

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

这是一个普遍的答案 。

例如,如果你的程序正在花费50%的时间在caching未命中,那么当你暂停程序计数器时,有50%的时间会在它正在等待引起内存读取的确切位置caching未命中。