为啥 Moose 代码这么慢?

Posted

技术标签:

【中文标题】为啥 Moose 代码这么慢?【英文标题】:Why is Moose code so slow?为什么 Moose 代码这么慢? 【发布时间】:2011-04-24 07:47:04 【问题描述】:

我正在尝试解析一个大型 XML 文件。我使用 XML::SAX 阅读它(使用 Expat,而不是 perl 实现)并将所有二级及以下节点放入我的“节点”类:

package Node;
use Moose;

has "name" =>
(
    isa =>  "Str",
    reader  => 'getName'
);

has "text" =>
( 
  is    =>  "rw",
  isa   =>  "Str"
);

has "attrs" =>
(
    is  =>  "rw",
    isa =>  "HashRef[Str]"

);

has "subNodes"  =>
(
    is  =>  "rw",
    isa =>  "ArrayRef[Node]",
    default => sub  [] 
);

sub subNode
   
  my ($self, $name) = @_;
  my $subNodeRef = $self->subNodes;
  my @matchingSubnodes = grep  $_->getName eq $name  @$subNodeRef;

  if (scalar(@matchingSubnodes) == 1)
  

    return $matchingSubnodes[0];
  
  return undef;



1;

在“end_element”子中,我检查这是否是我关心的节点,如果是,我会做一些进一步的处理。

这一切在我的测试文件上运行良好,但前天我把它扔到我的真实文件中,全部 1300 万行,而且它需要很长时间。它已经运行了超过 36 个小时。如何判断瓶颈是 Moose 还是 XML::SAX? Moose 总是这么慢,还是我用错了?

更新对 20,000 行数据子集进行分析表明,Moose 是瓶颈 - 特别是在 Class::MOP::Class::compute_all_applicable_attributes (13.9%) 和其他 Class和驼鹿类。

【问题讨论】:

驼鹿可能看起来很慢,但不要让他生气...... Class::MOP::Class::compute_all_applicable_attributes 如果您不像我在回答中建议的那样__PACKAGE__->meta->make_immutable 您的课程,则需要做很多事情。如果这样做,个人资料会如何变化? @Ether,那个是关于启动成本的。我的是关于运行成本,尤其是对象的创建和销毁。 我很想有一个你的 XML 数据的例子来测试它,我在 XML::Toolkit 中做了类似的事情(使用捆绑的 XML::Filter::Moose)并且会很好奇分析它。 您展示的代码相当短,为什么不使用传统的 perl5 对象重写它,看看它会如何改变呢?以非常信任的方式编写它,不检查任何类型约束或使用任何防御性编程实践。这将为您提供希望摆脱 perl 对象模型的速度上限 【参考方案1】:

虽然 Moose 在启动时做了很多工作,这有时让它看起来有点慢,但它生成的代码,尤其是属性访问器之类的东西,通常比普通 perl 程序员能够快得多写。因此,鉴于您的流程的运行时间很长,我怀疑 Moose 引起的任何开销都是相关的。

但是,从您显示的代码中,我无法真正说出您的瓶颈是什么,即使我坚信它不是 Moose。我还想指出,通过__PACKAGE__->meta->make_immutable 声明您的课程现在“已完成”允许 Moose 进行一些进一步的优化,但我仍然怀疑这是给您带来麻烦的原因。

您如何从数据中抽取一个较小的样本,这样您的程序将在合理的时间内完成,并在诸如Devel::NYTProf 之类的分析器中查看它。这将能够告诉您程序中的确切时间花费在哪里,因此您可以专门优化这些部分以获得最大可能的收益。

一种可能性是您使用的类型约束会减慢速度。实际上,在对它们的每一次写访问(或在类实例化)上彻底验证实例属性并不是大多数程序员通常会做的事情。如果您对数据的有效性有足够的把握,您可以尝试使用更简单的约束,例如 ArrayRef 而不是 ArrayRef[Node]。这样,只会检查属性值本身的类型,而不是该数组引用中每个元素的值。

但是,请分析您的代码。不要猜。

【讨论】:

NYTProf 比 DProf 好吗? __PACKAGE__->meta->make_immutable 让我的子集从 11 秒缩短到 6 秒。现在分析器说它在Moose::Meta::TypeConstraint::ArrayRef[Node] 上花费了 26% 的时间,所以我将尝试你的建议来放松对下一个限制。 放松约束没有任何明显的区别—​​—我的 20,000 行测试文件从 6.4 秒变为 6.3 秒。 @Paul:现在的时间分解是什么样子的?它现在应该主要用于 XML 处理,而不是 Moose 内脏。 参见:ttp://***.com/questions/3162390/is-moose-really-this-slow/3162676【参考方案2】:

我已经使用XML::Twig 745mb 文件成功编写了大型 XML 处理应用程序,在一个合理大小的盒子上运行不到一个小时。

但是正如其他用户已经提到的那样,您需要分析您的代码以找出导致问题的确切原因。

【讨论】:

【参考方案3】:

我高度怀疑您的速度问题不在于 Moose,而在于内存分配和磁盘交换。即使不执行 ->meta->make_immutable,根据您对 20K 子集的时间,您的脚本也应该在大约 2 小时内完成(((11 *(13_000_000 / 20_000))/60)== ~119 分钟)。通过执行 ->meta->make_immutable 它会将其减少到大约。 65分钟左右。

再次尝试运行你的大脚本,看看你的内存和交换在做什么,我怀疑你给你的磁盘带来了可怕的震动。

【讨论】:

Munin 说我在前 36 小时的跑步中几乎没有换过。从 10 月 10 日晚上到 12 月 12 日上午,请参阅 xcski.com/munin/xcski.com/allhats2.xcski.com-cpu.html、xcski.com/munin/xcski.com/allhats2.xcski.com-memory.html 和 xcski.com/munin/xcski.com/allhats2.xcski.com-swap.html。在我执行 make_immutable 之后,您可以在第二次运行期间(从 10 月 12 日上午 10 点左右开始)看到更多的交换使用,但这可能与我同时做的其他事情有关。

以上是关于为啥 Moose 代码这么慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这段代码运行得这么慢?

为啥这个 F# 代码这么慢?

我为类编写了这个汉明编码代码。为啥这么慢?

为啥 Tkinter 窗口打开这么慢?

为啥我的手动调优、支持 SSE 的代码这么慢?

为啥将影响 lambda 的代码编译为 std::function 这么慢,尤其是使用 Clang?