2

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

  • 导出子程序foo,导入有该子程序的模块。最后在你的 perl 脚本中调用它。
  • Object在你的 perl 脚本中创建一个模块,最后foo使用 that调用Object
  • foo使用它的路径直接调用,像这样myDir::Module::foo();

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

谢谢。

4

2 回答 2

6

在 Perl 中调用代码的最快和最好的方法是有区别的。


编辑:也请参阅 simbabques 的答案。他特别介绍了 #1 和 #3 之间的区别,以及为什么要使用其中任何一个。


#1、#3:函数调用

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

如果您正在调用的函数的名称在编译时已知,则 sub 将在编译时解析。这假设您不会自发地重新定义您的功能。如果确切的函数只在运行时知道,这只是一个散列查找。

函数的调用方式有以下三种:

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

第一个(大括号有时是可选的)是默认的,并根据子原型验证您的论点(不要使用原型)。此外,还构建了一个完整的调用堆栈帧(包含许多有用的调试信息)。这需要时间。

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

第三个是尾声。这从当前子返回,返回值为foo。这很快,因为原型被忽略了,并且可以重用当前调用堆栈帧。这通常不是很有用,而且语法很丑陋。内联代码大约快一个数量级(即在 Perl 中,我们更喜欢循环而不是递归☹)。

#2:方法调用

OO 的灵活性带来了巨大的性能代价:由于您调用消息的对象的类型直到运行时才知道,实际方法只能在运行时解析。

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

即使您知道类型,通常也不能将方法调用简化为函数调用。

如果$fooif of class Foo, andFoo::bar是 sub,Foo::bar($foo)则将跳过方法结果,甚至可能工作。但是,这会破坏封装,并且一旦Foo被子类化就会破坏。此外,如果Foo没有定义bar,这也不起作用,但该方法是在父类中定义的。

我通常赞成面向对象,直到从基准测试中清楚地看出这不会提供您需要的性能。

于 2013-02-07T11:37:43.553 回答
4
  • 导出子程序 foo,导入具有该子程序的模块。最后在你的 perl 脚本中调用它。

为了做到这一点,您将在实现 sub的 module/中使用Exporter 。package你告诉你的模块它将通过@EXPORT_OKand导出什么@EXPORT。如果您use是模块,则在编译时会将内容导入您当前的命名空间。以下 to 语句是等价的。

# 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 脚本中创建该模块的对象,最后使用该对象调用 foo。

这是面向对象的方法。它(如上所述)无法与其他方法相提并论。您不需要导入对象的方法。use您只需使用或(见上文)加载您的类(这是一个模块)require,创建一个实例并根据自己的喜好使用它的方法。但是,您需要一个面向对象的模块。如果您对它的工作原理感兴趣,请先查看perlootut

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

它实际上并不完全是它的路径,而是它的名称(空间)。例如,Data::DumperDumper.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 行脚本中,这无关紧要。在一个巨大文件中可能包含数千行代码和许多用例的遗留软件中,这非常重要。

我希望这可以帮助您做出决定。

于 2013-02-07T11:36:16.710 回答