【问题标题】:How can I speed up my Perl program?如何加快我的 Perl 程序?
【发布时间】:2010-09-15 16:22:10
【问题描述】:

这确实是两个问题,但它们非常相似,为了简单起见,我想我会把它们放在一起:

  • 首先:给定一个成熟的 Perl 项目,除了简单的代码内优化之外,还有哪些不错的方法可以加速它?

  • 其次:在 Perl 中从头开始编写程序时,有哪些好方法可以大大提高性能?

对于第一个问题,假设您收到了一个写得体面的项目,并且您需要提高性能,但您似乎无法通过重构/优化获得太多收益。在这种情况下,除了用 C 语言重写它之外,你会怎么做才能加快它的速度?

请远离一般的优化技术,除非它们是 Perl 特定的

我之前就Python 提出过这个问题,我认为对其他语言这样做可能会更好(我特别好奇是否有针对Perl 的psychopyrex 的推论)。

【问题讨论】:

    标签: perl optimization performance


    【解决方案1】:

    请记住优化俱乐部的规则:

    1. 优化俱乐部第一条规则 就是,你没有优化。
    2. 优化俱乐部的第二条规则是,你不优化而不测量。
    3. 如果您的应用运行速度快于底层传输协议,则优化结束。
    4. 一次一个因素。
    5. 没有市场机器人,没有市场机器人时间表。
    6. 只要需要,测试就会继续进行。
    7. 如果这是您在优化俱乐部的第一晚,您必须编写一个测试用例。

    因此,假设您确实有工作代码,请在 Devel::NYTProf 下运行您的程序。

    找到瓶颈。然后回到这里告诉我们它们是什么。

    如果您没有有工作代码,请先让它工作。您将做的最大的优化就是从不工作到工作。

    【讨论】:

    • 建议编辑:在“所以,假设...”之后添加“如果你没有工作代码,首先让它工作。你将做的最大的优化是从不工作开始上班。”
    • 反问:Perl 对这种优化技术有什么特别之处?
    • 就像“如果这是您在优化俱乐部的第一晚,您必须编写一个测试用例”。搏击俱乐部修改报价。
    【解决方案2】:

    这只有一半与您的问题有关 - 但为了文档的利益,我会在这里发布。

    最近的CentOS/Perl bugfix 将我们的应用程序的速度提高了两倍多。这是运行 CentOS Perl 并使用 bless/overload 功能的任何人的必需品。

    【讨论】:

    • 哦,好点。有关该错误和测试程序的更多信息,以查看它是否会影响您的代码,请参阅perlbuzz.com/2008/08/…
    【解决方案3】:

    Andy 已经提到了Devel::NYTProf。这很棒。真的,真的很棒。使用它。

    如果由于某种原因你不能使用Devel::NYTProf,那么你可以回退到旧的Devel::DProf,它已经成为Perl 的标准很长时间了。如果您有 true 函数(在数学意义上)需要很长时间来计算(例如,斐波那契数),那么您可能会发现 Memoize 提供了一些速度改进。

    许多糟糕的性能来自不适当的数据结构和算法。一门好的计算机科学课程可以在这里提供巨大的帮助。如果您有两种处理方式,并且想比较它们的性能,Benchmark 模块也很有用。

    以下Perl Tips 在这里也可能有用:

    免责声明:我写了上面的一些资源,所以我可能对它们有偏见。

    【讨论】:

      【解决方案4】:

      无需重写大块,您可以使用Inline::C 将任何单个、缓慢的子程序转换为C。或者直接使用XS。也可以使用 XS 增量转换 subs。例如,PPI/PPI::XS 就是这样做的。

      但迁移到另一种语言始终是最后的手段。也许你应该找一个专业的 Perl 程序员来看看你的代码?更有可能的是,他会发现一些严重影响你表现的特殊性。除此之外,请分析您的代码。请记住,没有灵丹妙药。

      关于 psyco 和 pyrex:不,Perl 没有等价物。

      【讨论】:

        【解决方案5】:

        有很多事情你可以改进,所以你首先必须弄清楚什么是慢的。其他人已经回答了这个问题。我在Mastering Perl 也谈到了这一点。

        在编写新代码时要考虑的事项的不完整列表:

        • 使用Devel::NYTProf 之类的内容进行简介,以查看您在代码中花费的大部分时间。有时这令人惊讶且易于修复。 Mastering Perl 对此有很多建议。

        • Perl 每次都必须编译源代码,编译速度会很慢。它必须找到所有文件等等。例如,参见 Jean-Louis Leroy 的 "A Timely Start",他通过优化 @INC 中的模块位置来加速一切。如果您的启动成本昂贵且不可避免,您还可以查看持久性 perls,例如 pperl、mod_perl 等。

        • 查看您使用的一些模块。他们是否有很长的依赖链只是为了做简单的事情?当然,我们不喜欢重新发明,但如果你想装在车上的轮子也有三艘船、五只山羊和一个芝士汉堡,也许你想建造自己的轮子(或者找一个不同的轮子) .

        • 方法调用可能很昂贵。例如,在 Perl::Critic 测试套件中,它对 isa 的调用会减慢速度。这并不是在所有情况下都可以真正避免的事情,但要牢记这一点。有人说过一句很棒的名言,比如“没有人介意放弃 2 倍;只有当你有 10 个人在做这件事时,这才是糟糕的。” :) Perl v5.22 对此进行了一些性能改进。

        • 如果您一遍又一遍地调用相同的昂贵方法但得到相同的答案,那么Memoize 之类的方法可能适合您。它是方法调用的代理。如果它真的是一个函数(意思是,相同的输入给出相同的输出而没有副作用),你真的不需要重复调​​用它。

        • Apache::DBI 等模块可以为您重用数据库句柄,以避免打开数据库连接的代价高昂。这是非常简单的代码,因此即使您不使用 Apache,也可以深入了解如何做到这一点。

        • Perl 不会为你做尾递归优化,所以不要从 Lisp 过来,以为你会制作这些超级快速的递归算法。您可以轻松地将它们转化为迭代解决方案(我们在 Intermediate Perl 中讨论了这一点。

        • 查看您的正则表达式。大量开放式量词(例如.*)会导致大量回溯。查看 Jeffrey Freidl'sMastering Regular Expressions 了解所有血腥细节(以及多种语言)。另请查看his regex website

        • 了解您的 perl 是如何编译的。你真的需要线程和DDEBUGGING吗?那些会让你慢一点。查看 perlbench 实用程序来比较不同的 perl 二进制文件。

        • 针对不同的 Perls 对您的应用程序进行基准测试。一些较新的版本有加速,但一些较旧的版本对于有限的操作集可能更快。因为我不知道你在做什么,所以我没有特别的建议。

        • 展开工作。你能在其他进程或远程计算机上做一些异步工作吗?让您的程序在其他人解决一些子问题时处理其他事情。 Perl 有几个异步和负载转移模块。但请注意,做好这些工作的脚手架可能会失去任何好处。

        【讨论】:

          【解决方案6】:

          分析您的应用程序 - 使用例如上面提到的分析器。然后你会看到时间的去向

          如果时间花在 CPU 使用率以外的事情上,则需要先减少这些时间 - CPU 易于扩展,而其他事情则不然。

          有几个操作特别慢,我发现:

          • keys() 在大哈希上非常糟糕
          • 使用Data::Dumper 进行调试。在大型结构上使用此功能非常慢。如果可以的话,避免它。我们已经看到了这样的代码:

            use Data::Dumper; 
            $debugstr = Dumper(\%bighash); 
            if ($debugflag_mostlyoff) { log($debugstr); } 
            
          • 大多数模块都有具有不同性能特征的替代品 - 有些确实非常糟糕。

          • 一些正则表达式可能非常慢(很多 .* 等),可以用更快的等效表达式替换。正则表达式很容易进行单元测试和性能测试(只需编写一个程序,针对大型模拟数据集在循环中运行它)。最好的正则表达式从可以很快测试的东西开始,例如文字字符串。有时最好不要先寻找你要找的东西,然后“向后看”以检查它是否真的是你要找的东西。优化正则表达式确实有点像我不太擅长的黑魔法。

          除非万不得已,否则不要考虑用 C 重写某些东西。从 Perl 调用 C(反之亦然)具有相对较大的开销。如果你能得到一个快速的 Perl 实现,那就更好了。

          如果您确实要在 C 中重写某些内容,请尝试以最小化调用开销的方式进行,并调用 perl 运行时(例如,SV* 函数主要复制字符串)。实现这一点的一种方法是制作一个 C 函数,它可以做更多的事情并减少调用它的次数。在内存中复制字符串并不酷。

          另一方面,用 C 重写某些东西会带来很大的风险,因为您可以引入新的故障模式,例如内存泄漏、崩溃、安全问题。

          【讨论】:

            【解决方案7】:

            Nicholas Clark 的演讲When perl is not quite fast enough (PDF) 是一篇非常值得一读的文章。有些观点有些过时,例如对 Devel::DProf 的引用,但请记住它是在 2002 年编写的。

            尽管如此,所涵盖的大部分材料仍然相关。

            【讨论】:

              【解决方案8】:

              方法和子程序调用在 Perl 中不是免费的。它们相对昂贵。因此,如果您的分析结果表明您在小型访问器方法中花费了相当多的运行时间,那么这可能是一个值得关注的微优化。

              然而,你应该做的是在这里替换诸如get_color()之类的访问器:

              package Car;
              # sub new {...}
              
              sub get_color {
                 my $self = shift;
                 return $self->{color};
              }
              
              package main;
              #...
              my $color = $car->get_color();
              

              具有破坏封装的直接访问:

              my $color = $car->{color};
              

              人们会认为这是不言而喻的,但人们也看到到处都是这样做的。这是你可以做的,使用Class::XSAccessor

              package Car;
              # sub new {...}
              use Class::XSAccessor
                getters => {
                  get_color => 'color',
                },
                setters => {
                  set_color => 'color',
                };
              

              这会创建在 XS 中实现的新方法 get- 和 set_color(),因此速度大约是手动版本的两倍。突变器(即“$car->color('red')”)也可用,链式方法也可用。

              根据您的应用程序,这可能会给您带来非常小的(但基本上是免费的)提升。除非您正在做一些特殊的事情,否则不要期望超过 1-2%。

              【讨论】:

                【解决方案9】:

                如果您的代码需要加速,那么您的测试套件很可能也需要加速。本次演讲涉及重点:

                Turbo Charged Test Suites

                【讨论】:

                  【解决方案10】:

                  最具成本效益的方法可能是考虑更快的硬件(=> 适当的硬件架构)。我说的不是更快的 CPU,而是更快的磁盘、更快的网络……更快的任何东西,真的,可以加快 I/O。

                  我多年前就经历过这种情况,当时我们将基于 XML 解析的应用程序(当时的尖端技术)从(快速且可靠!)Windows Server 转移到专用的、尽管有些过时的 SUN 平台周围有更快的 I/O。

                  一如既往,考虑

                  • 开发人员绩效(编码需要多长时间,问题有多复杂,结果是否可维护),
                  • 硬件性能,
                  • 软件性能

                  并改进对手头问题最有效的(成本!)...

                  【讨论】:

                  • 我喜欢这个想法,但应该说,有时在开发人员时间上花费更多的初始时间可能会导致从长远来看在硬件成本/加速方面的影响要小得多。这实际上取决于项目打算运行多长时间。
                  【解决方案11】:

                  让你的程序运行得更快的最好方法是让你的程序做更少的工作。为工作选择正确的算法。我见过很多缓慢的应用程序,因为它们在代码的某些区域选择了一种愚蠢的算法,被调用了数百万次。当您执行一百万*一百万次操作而不是一百万次操作时,您的程序运行速度将慢一百万倍。从字面上看。

                  例如,这是我看到的一些将元素插入排序列表的代码:

                  while(my $new_item = <>){
                      push @list, $new_item;
                      @list = sort @list;
                      ... use sorted list
                  }
                  

                  排序是 O(n log n)。插入排序列表是 O(log n)。

                  修正算法。

                  【讨论】:

                  • 最后断言插入排序列表是 O (log n) 是不准确的,除非您所说的“列表”是指例如。 “树”
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-02-20
                  • 2016-06-03
                  • 1970-01-01
                  • 2011-05-21
                  • 2019-03-09
                  相关资源
                  最近更新 更多