如何让程序跑得更快些?——试试Visual Studio中的性能分析工具 (By Jun Guo)

 咦,性能?我们又回到这个永恒的话题上了。Yep,大部分程序猿都对性能有着不懈追求。某国最喜欢“多快好省”,“多”和“省”我们是很难做到了,但让自己的程序跑得又快又好,则是我们最乐意干的活。干同样一件事情,别人的程序要跑1分钟,而自己的程序只要几秒钟,这是多爽的一件事啊(您打败了全国99%的程序猿……)!

      不过,话虽然这样说,但实际操作起来,效率优化并不是件容易的事。时间复杂度是最容易拉开效率差距的地方,但却也是最难拉开人与人之间差距的地方——毕竟很多问题的解决方案都比较成熟了,要能找到个时间复杂度更优的算法似乎不是一件容易的事情。然而,即便是复杂度相同的两份程序,由于程序常数不同,运行效率也往往有很大差异。试一下stdlib.h里的qsort,以及STL里的sort,就可以清楚地看出同样O(NlogN)的排序算法能有多大区别。不仅如此,实际程序往往还会涉及I/O、线程通信等操作,这些操作的快慢可不是靠复杂度分析就能得出结论的了。

      因此,如何在有限时间内尽可能地提高程序效率是个非常重要而复杂的问题。Visual Studio为我们提供了强大的性能分析工具,让我们能很快找出程序的性能瓶颈,从而能有针对性地改进程序常数。

        

      我们先看一个简单的例子。下述程序的功能非常简单:读入一个文本文件,统计各个单词出现的频率,并输出词频最高的100个单词。单词被简单定义为连续的大小写字母所组成的字符串,即I’m会被视为I和m两个单词。

复制代码
static void Main(string[] args)
{
    const int MAX_WORD_NUM = 1000000;
    const int BUFFER_SIZE = 100000;
    const int OUTPUT_NUM = 100;

     // 读入文件
    StreamReader sr = new StreamReader(new BufferedStream(new FileStream(
                "different.txt", FileMode.Open), BUFFER_SIZE));
    string data = sr.ReadToEnd();

      // 切割出单词
    string[] words = Regex.Split(data, "[^a-zA-Z]");

      // 统计单词词频
    Dictionary<string, int> dict = new Dictionary<string, int>((int)(MAX_WORD_NUM * 1.5));
    foreach (var word in words)
    {
        if (word == "")
            continue;

        if (dict.ContainsKey(word))
            dict[word]++;
        else
            dict.Add(word, 1);
    }

    List<Tuple<int, string>> list = new List<Tuple<int, string>>(MAX_WORD_NUM);
    foreach (var item in dict)
    {
        list.Add(Tuple.Create(item.Value, item.Key));
    }

    // 输出词频最高的前100个单词
    list.Sort();
    int count = 0;
    for (int i = list.Count - 1; i >= 0; i--)
    {
        Console.WriteLine(list[i].Item2 + " " + list[i].Item1);
count
++; if (count > OUTPUT_NUM) break; } sr.Close(); }
复制代码

      文本文件different.txt大约有100MB。好,现在我们来运行程序!大概过了10s,程序输出结果了。结果倒是正确的,但效率也未免太低了点(我之前的一篇博文有提到类似的词频统计程序,那个程序对320M的文本文件做词频统计大概只要4s,也就是说速度是上述程序的8倍)。OK,那上述程序的问题到底出在哪里?大家众说纷纭,有的人吐槽文件读入,因为I/O非常缓慢;有的人说是Hash表的查询与存储操作比较耗时(虽说理想情况下是O(1),但有冲突时会恶化);也有的人认为是最后的排序消耗了大量时间,毕竟其复杂度最高(不计字符串长度,其他操作是O(N),排序是O(NlogN),确实高了点)。

     

      为了阻止大家继续吐槽,我们还是来试试性能分析工具好了。

      首先要确保编译程序时采用Release编译,之后在Visual Studio 2012中选中“分析”-->“启动性能向导”,可以看到下图:     

      

      我们看到有两种性能分析方法:

  • CPU采样
  • 检测

      简单来说,CPU采样就是程序运行时,Visual Studio会定时查看当前程序正在运行哪个函数,并记录下来。当程序运行结束后,Visual Studio就会得出一个关于程序运行时间分布的大致印象。这种做法的优点是不需要改动程序,运行较快,可以很快得出性能瓶颈。但这种方法不能得出精确数据,有时可能会有误差。

      而检测则指Visual Studio会将检测代码注入到每一个函数中,这样整个程序的一举一动都将被记录在案,程序的所有性能数据都可以被精准地测量。然而这种方法会极大增加程序的运行时间,对数据的分析时间也会变得很漫长。

 

      一般来说,我们会先用CPU采样的方式找到性能瓶颈,然后对特定的模块采用检测的方法进行详细分析。由于这两者方式的操作很类似,所以下文仅展示CPU采样的用法。对上述程序进行CPU采样后,我们可以看到如下报告:

      

      点击上图中的Main函数,我们可以查看更具体的报告,如下图:

      

      在上图最右侧的“已调用函数”中点击相应函数还可以跳转到函数内各行代码的耗时统计。由于上面的函数耗时统计已经足够我进行性能优化,对我而言暂时没必要具体到代码行,这里就不再赘述了。

       可以看到,排序确实占了很多运行时间。然而,却有一个出乎我们意料的存在——Regex.Split函数居然占了将近30%的运行时间,与此对比Hash表的查询与插入操作却仅仅占了1%左右的时间,至于I/O操作的开销更是不见踪影。也许有些人在一开始也确实猜到Split函数会比较耗时,但占用30%的时间恐怕还是绝大多数人始料未及的。

 

      我们不妨着手自己实现这个Spilt函数(不要吐槽我为什么不优先改进Sort,我在这里仅仅是展示一下嘛)。代码如下:

复制代码
// 切割出单词
List<string> words = new List<string>(MAX_DIFF_WORD_NUM * 10);
int curPos = data.Length - 1, lastPos = curPos;
while (curPos >= 0)
{
      while (curPos >= 0 && !char.IsLetter(data[curPos]))
            curPos--;
      lastPos = curPos;

      while (curPos >= 0 && char.IsLetter(data[curPos]))
            curPos--;
      words.Add(data.Substring(curPos + 1, lastPos - curPos));
}
复制代码

      之后重新运行一次性能分析。

      

      嗯,这次就合理多了,整个程序的时间基本花费在Sort上(毕竟我们还没有改进Sort),I/O操作的开销开始体现出来,而Spit函数所带来的巨大开销已经减少了许多。(为什么手动实现的Split较快呢?因为Regex.Split里检测[^a-zA-Z]的开销要远远大于!char.isLetter()的开销,52次运算 vs 4次运算哦。)

 

      OK,那么接下来的优化目标显然是Sort了。如何优化相信各位算法大神肯定都很清楚,因为我们只要取词频前100的单词,所以没必要完整地做排序,用个可以容纳100个元素的最小堆滚一遍即可。具体就不再赘述了。

 

      就这样,我们可以沿着“性能分析-->改进-->再性能分析”的流程,逐步提高程序的性能和我们自己的编程水平。

      要注意一点的是,写程序时最好不要没做分析就过早地进行“性能优化”,正如上文所提到的,虽然有人提到Hash表和I/O操作会影响性能,但从性能分析的结果来看却非如此。这两者所带来的时间开销非常少。如果不经分析就盲目优化,也许只会事倍功半。

      另外还有一点要注意的是,虽然性能分析工具指明了程序各个部分的耗时,但这也不意味我们改进效率一定要优先改进耗时最多的部分。固然,改进耗时最多的部分往往能得到最明显的效果,但这并不意味耗时最多的部分很容易改进。像上文所示的Split函数,虽然其耗时并非最多,但由于其改进非常简单,有时反而会成为我们优先改进的对象。在实际项目中,我们要在改进所能得到的效果以及改进所要投入的精力之间妥协,优先完成有能力做而效果又比较明显的性能优化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程,具备跨平台开发能力的大学生将具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
Visual Studio性能分析器是一个强大的工具,可以帮助开发人员识别和解决应用程序性能问题。面是使用Visual Studio性能分析器的一般步骤: 1. 打开Visual Studio,并打开要分析的项目。 2. 在菜单栏选择“调试”>“性能分析器”>“性能探查器”。 3. 在性能探查器窗口,您可以选择要分析的目标(例如,CPU使用情况、内存使用情况等)。 4. 点击“开始分析”按钮,Visual Studio将开始记录性能数据。 5. 运行您的应用程序,执行您想要分析的操作。 6. 在您完成操作后,点击“停止分析”按钮,Visual Studio将停止记录性能数据。 7. 分析结果将显示在性能探查器窗口。您可以查看各种图表和报告,以了解应用程序性能瓶颈和优化建议。 除了上述基本步骤外,Visual Studio性能分析器还提供了许多高级功能,例如: - 自定义配置:您可以选择要记录的性能指标、采样率等。 - 高级图表和报告:您可以查看各种图表和报告,如CPU使用情况、内存分配情况、函数调用图等。 - 分析会话:您可以保存和加载分析会话,以便稍后进行进一步分析。 - 代码跟踪:您可以查看代码性能瓶颈,并进行深入的分析和优化。 总之,Visual Studio性能分析器是一个强大的工具,可以帮助开发人员识别和解决应用程序性能问题,从而提高应用程序性能和响应能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值