用C ++记录代码执行

时间:2011-04-14 13:46:08

标签: c++ logging gprof callgrind

多次使用 gprof callgrind ,我得出了(明显的)结论,在处理大型时我无法有效地使用它们(如在CAD程序中那样)加载整车)程序。我想也许,我可以使用一些C / C ++ MACRO 魔法,并以某种方式构建一个简单(但很好)的日志记录机制。例如,可以使用以下宏调用函数:

#define CALL_FUN(fun_name, ...) \
    fun_name (__VA_ARGS__);

我们可以在函数调用之前和之后添加一些时钟/定时内容,这样每个使用CALL_FUN调用的函数都会得到时间,例如

#define CALL_FUN(fun_name, ...) \
   time_t(&t0);                 \
   fun_name (__VA_ARGS__);      \
   time_t(&t1);

变量t0,t1可以在全局日志记录对象中找到。该日志记录对象还可以保存通过 CALL_FUN 调用的每个函数的调用图。之后,该对象可以用(特定格式化的)文件编写,并从其他程序解析。

所以这是我的(第一个)问题:你觉得这种方法易于处理吗?如果是,如何增强,如果没有,你能提出更好的方法来衡量时间和记录调用图吗?

一位同事提出了另一种处理这个问题的方法,即用每个函数(我们都记录日志)的特定注释进行注释。然后,在make过程中,必须运行一个特殊的预处理器,解析每个源文件,为我们要记录的每个函数添加日志记录逻辑,使用新添加的(解析)代码创建一个新的源文件,然后构建该代码。我想在整个地方阅读CALL_FUN ......宏(我的提议)并不是最好的方法,他的方法可以解决这个问题。那你对这种方法有什么看法?

PS:我不太熟悉C / C ++ MACRO的陷阱,所以如果可以使用其他方法开发,请说出来。

谢谢。

6 个答案:

答案 0 :(得分:2)

你可以做一些C ++魔法来嵌入一个日志对象。

之类的东西
class CDebug 
{
CDebug() { ... log somehow ... }
~CDebug() { ... log somehow ... }

};

在您的函数中,您只需编写

void foo()
{
   CDebug dbg;
    ...

   you could add some debug info


   dbg.heythishappened()

   ...
}  // not dtor is called or if function is interrupted called from elsewhere.

答案 1 :(得分:1)

让我假设您这样做的原因是您希望找到任何性能问题(瓶颈),以便您可以修复它们以获得更高的性能。

与测量速度或获取覆盖信息相反。

您似乎在想这样做的方法是记录函数调用的历史记录并测量每次调用所需的时间。

有一种不同的方法。 它基于这样的想法,主要是程序走一个大的调用树。 如果浪费时间,那是因为呼叫树比必要的更加浓密, 并且在被浪费的时间内,正在进行浪费的代码在堆栈中可见。 它可以是终端指令,但更可能是函数调用,几乎在堆栈的任何级别。 简单地在调试器下暂停几次程序最终会显示它。 你看到它做的任何事情,在多个堆栈样本上,如果你可以改进它,将加快程序。 无论是否花费在CPU,I / O或其他任何消耗挂钟时间的时间,它都能正常工作。 没有向您展示的内容是您不需要知道的大量内容。 唯一可以显示瓶颈的方法是,如果它们非常小, 在这种情况下,代码非常接近最佳。

Here's more of an explanation.

答案 2 :(得分:1)

许多不错的工业库都将函数的声明和定义包含在void宏中,以防万一。如果您的代码已经是这样 - 继续使用一些简单的异步跟踪记录器调试您的性能问题。如果没有 - 插入这样的宏可能是一个不可接受的耗时。

我可以理解在valgrind下运行1Mx1M矩阵求解器的痛苦,所以我建议从所谓的“蒙特卡罗分析方法”开始 - 重复启动过程并并行运行pstack,比如说每秒。因此,您将有N个堆栈转储(N可能非常重要)。然后,数学方法是计算每个堆栈的相对频率,并得出最频繁的结论。在实践中,你要么立即看到瓶颈,要么就是,你切换到bisection,gprof,最后转到valgrind的工具集。

答案 3 :(得分:1)

我有点迟了,但这就是我为此所做的事情:

在Windows上有一个/Gh compiler switch,它使编译器在每个函数的开头插入一个隐藏的_penter函数。还有一个开关,用于在每个函数结束时获得_pexit call

您可以利用它来获取每个函数调用的回调。这是一篇包含更多详细信息和示例源代码的文章:

http://www.johnpanzer.com/aci_cuj/index.html

我在自定义日志系统中使用这种方法,用于在环形缓冲区中存储最后几千个函数调用。这对于崩溃调试(与MiniDumps结合使用)非常有用。

关于此的一些注释:

  • 性能影响很大程度上取决于您的回调代码。你需要保持尽可能简单。
  • 您只需将功能地址和模块基地址存储在日志文件中即可。然后,您可以稍后使用Debug Interface Access SDK从地址中获取函数名称(通过PDB文件)。

所有这些对我来说都非常好。

答案 4 :(得分:0)

虽然我认为要比gprof更好地做任何事情,但你可以创建一个特殊的类LOG,并在你要记录的每个函数的开头实例化它。

class LOG {
    LOG(const char* ...) {
        // log time_t of the beginning of the call
    }
    ~LOG(const char* ...) {
        // calculate the total time spent,
        //by difference between current time and that saved in the constructor
    }
};

void somefunction() {
    LOG log(__FUNCTION__, __FILE__, ...);
    .. do other things
}

现在,您可以将此方法与您提到的预处理方法集成。只需在要记录的每个函数的开头添加这样的内容:

// ### LOG

然后在调试版本中自动替换字符串(不要太难)。

答案 5 :(得分:0)

可能你应该使用分析器。 AQTime对于Visual Studio来说是一个相对较好的。 (如果你有VS2010旗舰版,你已经有了一个探查器。)