为什么我的分析类非常慢?

时间:2010-11-20 17:09:18

标签: c++ visual-c++ profiling

我从未做过剖析。昨天我编写了一个ProfilingTimer类,其中包含一个静态时间表(一个map< std :: string,long long>)用于时间存储。

构造函数存储起始刻度,析构函数计算经过的时间并将其添加到地图中:

ProfilingTimer::ProfilingTimer(std::string name)
 : mLocalNameLength(name.length())
{
 sNestedName += name;
 sNestedName += " > ";

 mStartTick = Platform::GetTimerTicks();
}

ProfilingTimer::~ProfilingTimer()
{
 long long totalTicks = Platform::GetTimerTicks() - mStartTick;

 sTimetable[sNestedName] += totalTicks;

 sNestedName.erase(sNestedName.length() - mLocalNameLength - 3);
}

在我要配置的每个功能(或{block})中,我需要添加:

ProfilingTimer _ProfilingTimer("identifier");

当我从Visual C ++ 2010 Professional构建发行版时,此分析工作正常。但是当我构建为Debug时,我得到了一个巨大的fps下降(从63下降到~20)。

这些是我打印时间表(Debug build)时得到的数字:

Update() > Tower::Update > : 2551 ms (84100m%)
Update() > Tower::Update > Tower::Update1 > : 1313 ms (43284m%)
Update() > Tower::Update > Tower::Update1 > Tower::FindNewTarget > : 6 ms (204m%)
Update() > Tower::Update > Tower::Update1 > Tower::HasTargetInRange > : 5 ms (184m%)
Update() > Tower::Update > Tower::Update2 > : 659 ms (21756m%)
Update() > Tower::Update > Tower::Update2 > Tower::HasTargetInRange > : 5 ms (187m%)

Update1和Update2分别是Update的前半部分和后半部分。为什么他们加起来不是84.1%?

仍然这84%是一个巨大的数字 - 在发布版本中我得到了这个输出:

Update() > : 770 ms (1549m%)
Update() > Tower::Update > : 722 ms (1452m%)
Update() > Tower::Update > Tower::FindNewTarget > : 44 ms (89m%)
Update() > Tower::Update > Tower::HasTargetInRange > : 92 ms (187m%)

1,4%而非84,1%。这是一个巨大的差异!

任何人都知道为什么?

编辑:我认为发布比Debug快得多,但为什么这个分析如此耗时? std :: map the time hogger还是我做错了什么?

编辑:更新了代码。不需要启动,现在存储mLocalName的长度而不是实际的字符串。

3 个答案:

答案 0 :(得分:2)

Microsoft在调试模式下为其容器库添加了大量安全检查。这是有益的。你宁愿在诸如vector::operator[]之类的函数中捕获越界异常等,而不是破译内存损坏(仍然会建议调用vector::at。)但是,还有很多其他的事情要进入插入影响代码及其性能的调试器挂钩。

答案 1 :(得分:2)

使用长字符串作为std::map的索引可能不是执行此操作的最快方法。具有共同开头的长字符串意味着每次比较其中两个字符串时,必须查看许多字符以查看字符串是否相等。 std::map基本上是一个二叉树,并在每个查找/插入上进行O(log(n))比较。使用较短或数字键可以加快所有地图操作。

此外,使用std::unordered_map可能(或可能不会)提高速度,具体取决于地图包含的元素数量以及使用的密钥类型。

在分析类中,构造函数应该将名称作为引用,以避免创建该字符串的不必要副本:

ProfilingTimer::ProfilingTimer(const std::string &name)

通常避免不必要的副本是个好主意。例如,您可能并不真正需要mLocalName成员,而只是存储字符串长度就足够了。

可能经常调用分析函数,因此在程序运行时会出现小的延迟。

答案 2 :(得分:2)

您的代码存在一些性能问题。

  1. 你在ProfilingTimer构造函数中不必要地构造了一个std :: string。我建议使用const char *作为参数,并使用自定义Twine/Rope结构来进行追加。
  2. 为什么mLocalName甚至存在?请直接参考name
  3. 如前所述,不要在调试模式下进行配置。这太无用了。
  4. 地图实际上很慢。我建议使用哈希表。不幸的是,实现是编译器特定的如果您使用的是Microsoft,我相信他们可以unordered_map供您使用。
  5. 不使用sTimetable[sNestedName] = 0;,而是使用已检索的迭代器。

    Timetable::iterator loc = sTimetable.find(sNestedName);
    if(loc == sTimetable.end())
        sTimetable[sNestedName] = 0;
    
  6. 附录:Visual Studio最后检查了一个分析器。为什么不用它?