调用子程序的最快方法

时间:2013-02-07 09:55:40

标签: perl module subroutine

据我所知,在Perl中,我们可以使用这些技术从模块调用子例程:

  • 导出子例程foo,导入具有此子例程的模块。最后在你的perl脚本中调用它。
  • 在perl脚本中创建该模块的Object,最后使用foo调用Object
  • 使用其路径直接呼叫foo,例如myDir::Module::foo();

如果我总是感到困惑,这是调用子程序foo的更好方法。 如果我有一个动态脚本,我从浏览器而不是命令行运行,那么应采用哪种方法,以便脚本花费更少的时间。

感谢。

2 个答案:

答案 0 :(得分:6)

在Perl中调用代码的最快和最好的方法之间存在差异。


编辑:请参阅simbabques回答。他特别介绍了#1和#3之间的差异,以及为什么要使用它们。


#1,#3:函数调用

您的#1和#3是相同的:子例程在全局可见的命名空间中具有唯一的名称。许多名称可以通过别名映射到一个子例程,或者导入模块。

如果在编译时已知要调用的函数的名称,则将在编译时解析该子函数。这假设您不会自发地重新定义您的功能。如果确切的函数仅在运行时知道,那么这只是哈希查找。

有三种方法可以调用函数:

foo(@args);
&foo(@args);
@_ = @args; goto &foo;

第一个(大括号有时是可选的)是默认的,并且针对子原型验证你的参数(不要使用原型)。此外,构造了整个调用堆栈帧(具有许多有用的调试信息)。这需要时间。

第二个跳过原型验证,并假设您知道自己在做什么。这稍快一点。我认为这是草率的风格。

三号是尾调用。这将从当前sub返回,返回值为foo。这很快,因为原型被忽略,并且可以重用当前的调用堆栈帧。这经常没用,而且语法很难看。内联代码的速度提高了一个数量级(即在Perl中,我们更喜欢循环而不是递归。)。

#2:方法调用

OO的灵活性是以极高的性价格出现的:由于您调用消息的对象类型在运行时才知道,实际方法只能在运行时解析。

这意味着$foo->bar()会在bar$foo编辑的包中查找函数bless。如果在那里找不到它,它将在父类中搜索。这很慢。如果要使用OO,请注意浅层次结构(→减少查找次数)。还要注意Perls默认的方法解析顺序是不寻常的。

即使您知道类型,也不能减少对函数调用的方法调用。

如果$foo属于Foo类,而Foo::bar是子类,则Foo::bar($foo)将跳过方法结果,甚至可能有效。但是,这会破坏封装,并且会在Foo子类化后中断。此外,如果Foo未定义bar,则此方法无效,但该方法是在父类中定义的。

我一般都支持面向对象,直到从基准测试中可以看出这不会提供您所需的性能。

答案 1 :(得分:4)

  
      
  • 导出子程序foo,导入具有该子程序的模块。最后在你的perl脚本中调用它。
  •   

为了做到这一点,你可以在实现sub的模块/ package中使用Exporter。您可以通过@EXPORT_OK@EXPORT告诉您的模块将导出哪些内容。如果你use模块,那么东西会在编译时导入到你当前的命名空间。以下语句是等效的。

# This is the same...
use Module;
# ... as this

BEGIN {
  require Module;
  Module->import();
}

如果你有要在主脚本中使用的东西,或者你打算经常使用,你想要这样做。一些示例包括List::UtilData::Dumperuse feature 'say'。当然,您也可以在其他模块中使用它。

use Data::Dumper;
use List::Util qw(max);
use feature qw(say);

my @foo = (1, 2, 3, 4, 5, 23);
print Dumper \@foo;
say max(@foo);

问题是,在这里,你污染了'您的命名空间如果必须,请执行此操作,但请记住它在编译时发生,因此不是条件。你不能说

if ($foo) {
  use Some::Module 'foo';
  foo($foo);
} else {
  use Something::Else 'bar';
  bar();
}

它会在编译时加载Some::Module Something::Else,从而增加程序消耗的时间和内存。这种情况当然会奏效,但效率不高。

  
      
  • 在perl脚本中创建该模块的Object,最后使用该Object调用foo。
  •   

这是OOp方法。它(如上所述)不能与其他方法相比。您不需要导入对象的方法。您只需使用userequire(参见上文)加载您的类(这是一个模块),创建一个实例并根据自己的喜好使用它的方法。但是,您需要一个面向对象的模块。如果您对如何运作感兴趣,请先查看perlootut

  
      
  • 使用其路径直接调用foo,如myDir :: Module :: foo();.
  •   

它实际上不是它的路径,而是它的名称(空间)。例如,Data :: Dumper Dumper.pm位于Data目录中的lib文件夹中。但这并不重要。

与第一种方法的主要区别在于您省略了导入部分。如果您想要构建有条件地加载某些模块的东西,或者如果您在一个巨大的(可能是遗留的)应用程序中并且不想污染命名空间,这将非常有用。

if ($order_has_some_condition) {
  require Very::Long::NameSpace::For::This::Condition::Module;
  Very::Long::NameSpace::For::This::Condition::Module::do_stuff_with_an_order($order);
}

想象一下,这段代码是在一个遗留的子代码中,有2k行并且有很多东西正在进行中,大多数代码在我们的案例中从未被调用过。我们不希望use我们的模块,使其可用于这一大块代码中处理的100种不同案例中的每一种。相反,我们只想加载它,如果我们真的需要它。现在我们require模块并使用全名直接调用它。

在结论中,第一种和第三种方式都有其优点。它们都需要存在,并且如果合适,它们都应该被使用。在某些情况下,它只是味道,但在其他情况下,它是有道理的决定。第二种,OOp方法完全不同。

没有真正的速度差异,正如Borodin所说,Perl很快。当然,如果你没有import的东西,你就不必付钱了。进口。在一个10行的剧本中,这并不重要。在遗留软件中,可能存在数千行代码和一个巨大文件中的许多用例,这很重要。

我希望这有助于你做出决定。