Backtrace查询极慢

时间:2015-05-12 21:44:16

标签: c++ linux g++ profiling glibc

(注:以下提及execinfo / backtrace,但这只是一个例子。问题中的行为出现在各种图书馆中。)

考虑一个实用程序库,它跟踪与之链接的某些应用程序的资源分配。当函数分配和释放资源时,它们会调用记录操作细节的跟踪函数,以及可用于重建调用路径的一些信息。有时,会通过呼叫路径查询库的操作细分。

在这个问题的设置中,跟踪需要低开销,但不一定要查询。因此,为了跟踪,我存储了识别呼叫路径的最小信息,例如,通过呼叫execinfo / backtrace。翻译符号,解除标记等等都会延迟到查询,而不是此问题的一部分。

令我惊讶的是,相对于malloc调用,简单地调用backtrace会使执行速度降低~4000%(!)。由于backtrace将请求的(最大)堆栈深度作为参数,并且可以通过具有不同堆栈深度的调用路径调用它,因此我试图了解这些参数如何影响其性能。据我所知,简单地以任何方式调用此函数都会受到巨大的惩罚。

对于测量,我编写了以下简单代码(另请参阅full version):

const size_t max_backtrace_size = 100;
void *backtrace_buf[max_backtrace_size];   
static void track()
{
    if(backtrace_size > 0)
        ::backtrace(backtrace_buf, backtrace_size);
}

static void op(size_t i)
{
    if(i == 0)
    {
        track();
        return;
    }
    op(i - 1);
}

这两个函数中的第一个track模拟实际跟踪(请注意backtrace_size == 0完全禁用对backtrace的调用);第二个,op是一个递归,通过调用track来终止。使用这两个函数,我改变了参数并测量了结果(另见the IPython Notebook)。

下图显示了跟踪时间,作为不同堆栈大小的函数,每个调用backtracebacktrace_size == 1或不调用它(它的时间非常短,位于X上轴,在图中很难看到)。 backtrace即使用小参数调用也会产生巨大的开销。

time as function of stack depth

下图进一步显示了开销,现在作为回溯大小和堆栈深度的函数。再一次,只需调用此函数就会有一个巨大的跳跃。

time as a function of stack depth and backtrace size

  1. 有没有任何技术方法可以减少寻找回溯的开销? (可能是不同的库,或不同的构建设置。)

  2. 在没有1.的情况下,顶部的问题是否存在完全不同的可行替代方案?

3 个答案:

答案 0 :(得分:1)

  

令我惊讶的是,简单地调用backtrace会使执行速度减慢~4000%(!)。

这种说法本身毫无意义。即使backtrace()归结为单个指令,如果您的其他代码根本不包含任何指令,它仍将构成+INF开销。

40倍的开销可能意味着您尝试占用的资源非常便宜。如果是这样,可能不是该资源的每个实例都必须考虑?例如,您是否可以为每个N资源分配记录堆栈跟踪?

  

有没有任何技术方法可以减少寻找回溯的开销? (可能是不同的库,或者不同的构建设置。)

有几种可能性需要考虑。

假设您在询问Linux / x86_64,backtrace速度慢的一个原因是,在没有帧指针的情况下,它必须找到并解释展开信息。另一个原因:它主要用于处理异常,并且从未针对速度进行优化。

如果您完全控制将使用您的库的应用程序,使用-fno-omit-frame-pointer构建所有将允许您使用更快的基于帧的开卷。

如果你不能这样做,libunwind backtrace可能明显快于GLIBC(虽然它仍然无法击败基于帧的开卷)。

答案 1 :(得分:1)

你要说的是backtracemalloc花费的时间更长。 如果您需要它告诉您的内容,则需要付出代价。

它是否有意提高效率,因此可以高频率调用?

我确定它的目的是诊断内存问题等问题,或者你为此调用很多问题,或性能问题,而你不需要多次调用它。

当您发现问题时,您可以修复它们, 当你不再需要backtrace时,你可以停止呼叫它,并且很高兴它能帮助你找到它们。

答案 2 :(得分:0)

如果您使用的是libunwind,请确保使用UNW_LOCAL_ONLY定义构建代码:

#define UNW_LOCAL_ONLY
#include <unwind/libunwind.h>

我发现它还有助于添加&#34; - 禁用阻止信号&#34;配置命令 - 没有它,libunwind最终会花费大量时间来阻止和解锁部分代码周围的信号。在ARM(我测试的地方)这非常重要。

即便在此之后,我认为libunwind的性能仍有一些改进。我正在使用perf来尝试更多地挖掘它。