在C ++中记录代码执行

| 经过多次使用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调用的每个函数的调用图。之后,可以将该对象写入(特定格式)文件中,并从其他程序进行解析。 所以这是我的(第一个)问题:您认为这种方法易于处理吗?如果是,则如何增强它;如果不是,则可以提出一种更好的方法来测量时间和记录通话记录吗? 一位同事提出了另一种解决此问题的方法,该方法在每个功能(我们关心要记录的功能)上添加特定注释。然后,在制作过程中,必须运行特殊的预处理器,解析每个源文件,为我们要记录的每个函数添加日志记录逻辑,使用新添加的(解析)代码创建一个新的源文件,然后构建该代码。我想到处阅读CALL_FUN ...宏(我的建议)不是最好的方法,而他的方法可以解决此问题。那么您对这种方法有何看法? PS:我不熟悉C / C ++ MACRO的陷阱,因此,如果可以使用其他方法进行开发,请这样说。 谢谢。     
已邀请:
好吧,您可以做一些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.
    
为了防万一,许多不错的工业库都将函数的声明和定义包装在void宏中。如果您的代码已经这样了,请继续使用一些简单的异步跟踪记录器调试性能问题。如果否,则此类宏的后插入操作可能会非常耗时。 我可以理解在valgrind下运行1Mx1M矩阵求解器的痛苦,所以我建议从所谓的“ Monte Carlo分析方法”开始-启动进程并重复并行运行pstack,例如每秒一次。结果,您将有N个堆栈转储(N可能非常重要)。然后,数学方法将是计算每个电池组的相对频率,并得出最频繁的频率的结论。在实践中,您要么立即看到瓶颈,要么如果没有,则切换到二等分,gprof,最后切换到valgrind的工具集。     
让我假设您执行此操作的原因是要查找任何性能问题(瓶颈),以便您可以解决这些问题以获得更高的性能。 与测量速度或获取覆盖率信息相反。 看来您正在考虑执行此操作的方法是记录函数调用的历史记录并测量每个调用花费的时间。 有另一种方法。 它基于这样的思想,即程序主要走一个大调用树。 如果浪费时间,那是因为调用树比需要的树丛浓密, 并且在浪费的时间中,正在浪费的代码在堆栈上可见。 它几乎可以在堆栈的任何级别上作为终端指令,但更可能是函数调用。 简单地在调试器下暂停程序几次,最终将显示它。 如果您能改进它,那么在一个以上的堆栈样本上,只要您看到它所做的任何事情,都可以加快程序的速度。 无论是否在CPU,I / O或其他消耗挂钟时间的时间上花费,它都可以工作。 它没有向您显示的是大量您不需要知道的东西。 无法显示瓶颈的唯一方法是,如果瓶颈很小, 在这种情况下,代码几乎是最佳的。 这里是更多的解释。     
我来晚了,但这是我正在做的事情: 在Windows上,有一个/ Gh编译器开关,它可使编译器在每个函数的开头插入一个隐藏的_penter函数。每个函数的末尾都有一个用于获取_pexit调用的开关。 您可以利用它来获取每个函数调用的回调。这是具有更多详细信息和示例源代码的文章: http://www.johnpanzer.com/aci_cuj/index.html 我在自定义日志记录系统中使用此方法将最后几千个函数调用存储在环形缓冲区中。事实证明,这对于崩溃调试(与MiniDumps结合使用)很有用。 一些注意事项: 对性能的影响在很大程度上取决于您的回调代码。您需要使其尽可能简单。 您只需要在日志文件中存储功能地址和模块基地址。然后,您以后可以使用Debug Interface Access SDK从地址(通过PDB文件)中获取函数名称。 所有这些对我来说都非常有效。     
尽管我认为做任何事情都比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
然后在调试版本中自动替换该字符串(应该不难)。     
可能是您应该使用探查器。对于Visual Studio而言,AQTime是一个相对不错的工具。 (如果您拥有VS2010 Ultimate,则您已经有一个探查器。)     

要回复问题请先登录注册