需要分析帮助

时间:2010-06-15 16:51:18

标签: c++ c visual-studio-2008 vtune

我有一个分析问题 - 想象一下我有以下代码......

void main()
{
    well_written_function();
    badly_written_function();
}
void well_written_function()
{
    for (a small number)
    {
        highly_optimised_subroutine();
    }
}
void badly_written_function()
{
    for (a wastefully and unnecessarily large number)
    {
        highly_optimised_subroutine();
    }
}
void highly_optimised_subroutine()
{
    // lots of code
}

如果我在vtune(或其他分析器)下运行它,很难发现任何错误。所有热点都将出现在标记为“//批量代码”的部分中,该部分已经过优化。 badly_written_function()不会以任何方式突出显示,即使它是造成所有麻烦的原因。

是否有一些vtune功能可以帮助我找到问题?

是否有某种模式可以让我找到badly_written_function()及其所有子功能所花费的时间?

4 个答案:

答案 0 :(得分:1)

滚动您自己的 简单 分析器并不难。插入main():

int main()
{
    profileCpuUsage(1);                 // start timer #1
    well_written_function();
    profileCpuUsage(2);                 // stop timer #1, and start timer #2
    badly_written_function();
    profileCpuUsage(-1);                // print stats for timers #1 and #2
    return 0;
}

其中:

#define NUMBER(a) ((int)(sizeof(a) / sizeof(a)[0]))

void profileCpuUsage(int slice)
{
    static struct {
        int iterations;
        double elapsedTime;
    } slices[30];                             // 0 is a don't care slice

    if (slice < 0) {                          // -1 = print
        if (slices[0].iterations)
            for (slice = 1; slice < NUMBER(slices); slice++)
                printf("Slice %2d  Iterations %7d  Seconds %7.3f\n", slice,
                    slices[slice].iterations, slices[slice].elapsedTime);
    }
    else {
        static int i;                         // = previous slice
        static double t;                      // = previous t1
        const double t1 = realElapsedTime();  // see below for definition
        assert (slice < NUMBER(slices));
        slices[i].iterations  += 1;
        slices[i].elapsedTime += t1 - t;      // i = 0 first time through
        i = slice;
        t = t1;
    }
}

现在无可否认,在您的简单示例中使用此profileCpuUsage()并没有带来太多好处。它的缺点是要求您通过在适当的位置调用profileCpuUsage()来手动检测代码。

但优势包括:

  • 您可以计算任何代码片段,而不仅仅是程序。
  • 可以快速添加和删除,因为您进行二进制搜索以查找和/或删除代码热点。
  • 它只关注您感兴趣的代码。
  • 便携式!
  • KISS

一个棘手的非便携式事物是定义函数realElapsedTime(),以便它提供足够的粒度来获得有效时间。这通常适用于我(使用CYGWIN下的Windows API):

#include <windows.h>
double realElapsedTime(void)   // <-- granularity about 50 microsec on test machines
{
    static LARGE_INTEGER freq, start;
    LARGE_INTEGER count;
    if (!QueryPerformanceCounter(&count))
        assert(0 && "QueryPerformanceCounter");
    if (!freq.QuadPart) {      // one time initialization
        if (!QueryPerformanceFrequency(&freq))
            assert(0 && "QueryPerformanceFrequency");
        start = count;
    }
    return (double)(count.QuadPart - start.QuadPart) / freq.QuadPart;
}

对于直接Unix,有一个共同点:

double realElapsedTime(void)                      // returns 0 first time called
{
    static struct timeval t0;
    struct timeval tv;
    gettimeofday(&tv, 0);
    if (!t0.tv_sec)
        t0 = tv;
    return tv.tv_sec - t0.tv_sec + (tv.tv_usec - t0.tv_usec) / 1000000.;
}

realElapsedTime()给出了挂钟时间,而不是处理时间,这通常是我想要的。

还有其他不太便携的方法可以使用RDTSC实现更精细的粒度;请参阅示例http://en.wikipedia.org/wiki/Time_Stamp_Counter及其链接,但我没有尝试过这些。

编辑: ravenspoint的非常好的答案似乎与我的不太相似。 他的回答使用了很好的描述性字符串,而不仅仅是丑陋的数字,我常常对此感到沮丧。但这可以用大约十几个额外的行来修复(但这几乎双倍行数!)。

请注意,我们要避免使用malloc(),我甚至对strcmp()有点怀疑。因此切片的数量永远不会增加。并且哈希冲突只是标记它而不是被解决:人工分析器可以通过手动增加30的切片数量或通过更改描述来解决此问题。的 未测试

static unsigned gethash(const char *str)    // "djb2", for example 
{
    unsigned c, hash = 5381;
    while ((c = *str++))
        hash = ((hash << 5) + hash) + c;    // hash * 33 + c 
    return hash;
}

void profileCpuUsage(const char *description)
{
    static struct {
        int iterations;
        double elapsedTime;
        char description[20];               // added!
    } slices[30];

    if (!description) {
        // print stats, but using description, mostly unchanged...
    }
    else {
        const int slice = gethash(description) % NUMBER(slices);
        if (!slices[slice].description[0]) { // if new slice
            assert(strlen(description) < sizeof slices[slice].description);
            strcpy(slices[slice].description, description);
        }
        else if (!!strcmp(slices[slice].description, description)) {
            strcpy(slices[slice].description, "!!hash conflict!!");
        }
        // remainder unchanged...
    }
}

另一点是,通常您需要为发布版本禁用此分析;这也适用于ravenspoint的答案。这可以通过使用邪恶的宏来定义它来完成:

#define profileCpuUsage(foo)                // = nothing

如果这样做,您当然需要在定义中添加括号以禁用禁用宏:

void (profileCpuUsage)(const char *description)...

答案 1 :(得分:1)

这通常被称为“callgraph配置文件”,我相当确定Visual Studio会这样做。

答案 2 :(得分:1)

我可以建议我自己的开源分析器raven :: set :: cRunWatch吗?它专为解决此问题而设计,适用于使用Visual Studio 2008标准版的Windows,因此您无需为包含探查器的版本付费。

我已经使用了你的代码,稍微重新排列了它,因此它编译时没有前向声明并添加了对cRunWatch的必要调用

// RunWatchDemo.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

void highly_optimised_subroutine()
{
    raven::set::cRunWatch runwatch("highly_optimised_subroutine");
    Sleep( 2 );
}


void badly_written_function()
{
    raven::set::cRunWatch runwatch("badly_written_function");
    for (int k = 1; k < 1000; k++ )
    {
        highly_optimised_subroutine();
    }
}

void well_written_function()
{
    raven::set::cRunWatch runwatch("well_written_function");
   for (int k = 1; k < 10; k++ )
    {
        highly_optimised_subroutine();
    }
}


int _tmain(int argc, _TCHAR* argv[])
{
raven::set::cRunWatch::Start();

    well_written_function();
    badly_written_function();

raven::set::cRunWatch::Report();

    return 0;
}   

运行时会产生输出

raven::set::cRunWatch code timing profile
                    Scope   Calls       Mean (secs)     Total
highly_optimised_subroutine     1008    0.002921        2.944146
   badly_written_function        1      2.926662        2.926662
    well_written_function        1      0.026239        0.026239

这表明badly_written_function是非常接近的第二次用户,因此也是罪魁祸首。

您可以从here获取cRunWatch您将在用户指南中识别示例代码: - )

答案 3 :(得分:0)

通常,您需要观察函数的总时间而不是自身时间,以确保您正在查看包含被调用函数时间的时间。

在VTune中,我建议使用自上而下的标签。或者,甚至更好,如果您使用的是最新更新,请尝试新的实验性Caller-Callee视图。您可以在此处获取有关此内容的详细信息 - http://software.intel.com/en-us/forums/topic/376210。它会得到一个完整的函数列表及其总时间,以便您可以查看程序中最耗时的子树。