C++代码性能分析工具——Very Sleepy

6
我是一个关于性能分析的新手。我想优化我的代码以满足时间限制。我使用 Visual C++ 08 Express,因此必须下载一个性能分析器,我选择了 Very Sleepy。我搜索了一些资料但没有找到有用的 Sleepy 教程,以下是我的问题: 如何正确使用它?我大致理解了性能分析的概念,所以我按照“% 独占”排序找到了我的瓶颈。首先,在列表的顶部我有 ZwWaitForSingleObjectRtlEnterCriticalSectionoperator newRtlLeaveCriticalSectionprintf 和一些迭代器... 它们加起来占了大约60%,然后是我的第一个函数,也是 Child Calls 的第一位置。有人能够解释一下为什么会出现上述情况吗?它们是什么意思?如果我无法访问这 critical 60%,我该如何优化我的代码呢?(“源文件”未知...)另外,对于我的函数,我认为我可以获得每行的时间,但事实并非如此,例如算术或某些函数没有计时(未嵌套在未使用的 “if” 子句中)。最后,如何找出某行代码可以超快地执行,但被调用数千次,实际上成为瓶颈呢?
最后,Very Sleepy 是好的吗?还是有其他免费的替代品适用于我的平台?
非常感谢您的帮助!祝好!
        • 更新 - - - - -
我发现了另一个版本的性能分析器,叫做普通 Sleepy。它显示了一些代码片段被调用的次数以及行数(我猜测它指向了关键行)。所以在我的情况下... KiFastSystemCallRet 占了50%!它意味着它正在等待某些数据吗?如何改善这种情况?是否有一个合适的方法来跟踪是什么导致了这些多次调用,并最终删除或更改它们?

2
我使用Visual C++ 08 Express,因此不得不下载一个分析器,对我来说是Very Sleepy。我的建议是喝咖啡...通常两杯就足够了。效果可能因人而异。 - ScoPi
1
如果你想要一个替代Very Sleepy的工具,可以看看AMD的CodeAnalyst,它附带了一些教程,并且也适用于英特尔处理器(只是你不能获得所有高级的AMD选项)。 - Necrolis
追踪导致这些多次调用的可行方法。这正是暂停操作所告诉你的。你会在堆栈上看到 KiFastSystemCallRet,并且你会看到它是从哪里调用的,以及 那个 是从哪里调用的,等等。 - Mike Dunlavey
1
我在这里有一个带有一些改进的Very Sleepy分支链接 - Vladimir Panteleev
2个回答

7
我希望优化我的代码以满足时间限制。
你正在遇到这个行业中的一个持久问题。你想找到让你的代码运行时间更短的方法,而你(和许多人)认为(并且已经被教导)唯一的方法是通过各种测量来实现。
有一个少数派观点,它唯一值得推荐的是实际显著的结果加上一个坚不可摧的理论支持)。
如果你有一个“瓶颈”(而你可能有几个),它占用了一定比例的时间,比如30%。
只需将其视为要找到的漏洞
使用暂停按钮随机中断程序,仔细观察程序正在做什么以及为什么要这样做。 询问是否可以摆脱此问题。 重复以上步骤10次。平均来说,您将在3次暂停中看到问题。 如果您看到任何超过一次的活动,且不是真正必需的,则是速度缺陷。 这并不会精确告诉您问题造成了多大的代价,但它确实能够准确告诉您问题所在,并且值得修复。 您将以这种方式看到一些分析工具无法找到的问题,因为分析工具只是程序,不能对构成机会的事物有广泛的思考。
有些人风险规避,认为它可能不会带来足够的加速效果而不值得。 诚然,有小概率出现低回报的情况,但这就像投资一样。 理论上平均来说它是值得的,也有小概率出现高回报。 无论如何,如果您担心风险,进行更多的样本测试将消除您的担忧。
修复问题后,剩余的瓶颈所占比例更大了,因为它们没有变小,但整个程序确实变小了。 因此,在重复整个过程时,它们将更容易被找到。

有很多关于性能分析的文献,但很少有实际说明它在实践中可以实现多少加速。

这里有一个具体的例子,实现了近3个数量级的加速。


2
我曾使用GlowCode(商业产品,类似于Sleepy)来分析本地C++代码。您需要运行插装过程,然后执行程序,最后查看工具生成的数据。插装步骤在每个方法的入口点和出口点注入一个小的跟踪函数,并简单地测量每个函数完成所需的时间。
使用调用图分析工具,我列出了按“使用时间最长”到“使用时间最短”排序的方法,并且该工具还显示了调用计数。仅通过深入了解最高百分比例程,我就可以知道哪些方法使用了最多的时间。我发现有些方法非常慢,但是深入研究它们后,我发现它们正在等待用户输入或等待服务响应。而一些方法需要调用一些内部例程,每次调用都会重复几千次,因此需要很长时间。我们发现有人犯了一个编码错误,对于列表中的每个项目,他们都反复遍历了一个大型链表,而实际上他们只需要遍历一次即可。
如果按“最频繁调用”到“最少调用”排序,则可以看到从各处调用的一些微小函数(如迭代器方法next()等)。需要检查的是确保最常调用的函数真的很干净。在一个被调用500次以绘制屏幕的例程中节省一毫秒将使该屏幕加快半秒钟。这有助于您决定要花费精力的最重要的地方。
我看到了使用分析的两种常见方法。一种是进行一些“通用”分析,运行一组“正常”操作,并发现哪些方法最拖慢应用程序。另一种是进行具体的分析,关注用户对性能的特定抱怨,并运行这些函数以揭示它们的问题。
我想警告您的一件事是限制更改,只针对那些能够明显影响用户体验或系统吞吐量的更改。从鼠标单击中刮掉一毫秒对普通用户来说没有任何影响,因为人类反应时间并不那么快。赛车手的反应时间为8毫秒左右,一些精英的twitch游戏玩家甚至更快,但普通用户(如银行出纳员)的反应时间为20-30毫秒。其好处微乎其微。
进行二十个1毫秒的改进或进行一个20毫秒的改变将使系统更加响应。如果您可以进行单个大改进而不是许多小改进,则更便宜且更好。
同样,从处理每秒100个用户的服务中刮掉一毫秒将使其改进10%,这意味着您可以将服务改进为处理每秒110个用户。
担心的原因是,为了严格改善性能而进行的编码更改往往会通过增加复杂性来负面影响您的代码结构。比方说,你决定通过缓存结果来改进对数据库的调用。但是,当缓存变得无效时,你如何知道呢?难道需要添加清除缓存的机制吗?再比如,在金融交易中,遍历所有的行项目以产生递增的总额会非常慢,所以你决定保留一个runningTotal累加器以便更快地回答问题。现在,你必须修改runningTotal以适应所有种类的情况,比如行作废、反转、删除、修改和数量变化等等。这使得代码更加复杂和容易出错。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接