Perl 6中类型/约束会影响性能吗?是否有相关研究?

9
与 Perl 5 不同的是,Perl 6 引入了可选类型以及约束,例如:
# Perl 5
sub mySub {
   my $probability = $_[0];
   # Do stuff with $probability
}

# Perl 6 - using optional typing and constraints
sub mySub(Real $probability where 0 < * < 1) {
   # Do stuff with $probability
}

是否有研究调查了使用这些功能时,不同的Perl 6 VMs是否存在性能损失,并且它们有多大?

我正在寻找一些经过良好设计和跨VM的内容。


2
相关:lizmat在FOSDEM 2017上关于加速Perl 6的演讲。我认为其中有一些与你的问题相关的内容。但即使不是,你也可能会喜欢观看它。 :) 还有一个由Nine关于使用Perl 6进行Web开发的演讲,在那里他对比了Dancer和Mojo在Perl 5中的速度,并在Perl 6中进行了移植,并使用他的Perl 5 in Perl 6的东西来运行它,我认为那也谈到了类型,但视频还没有上传。 - simbabque
3
@Borodin: 这意味着,如果您犯了错误并向子程序传递了不可接受的值,它会提前死亡,并显示一条有用的错误消息(在某些情况下甚至是在编译时),而不是在程序执行的较晚阶段难以理解地崩溃和烧毁,或者更糟糕的是产生不正确/损坏的数据。 - smls
4
@DVK:我不知道有这样一项彻底的性能研究(而且随着Rakudo每个月变得更加优化,这将是一个移动目标)。但作为经验法则,“where”从句和“subset”类型总是会损害性能,因为它们被视为不透明代码,每次检查类型时都必须运行,而声明性类型约束实际上在某些情况下可以改善性能,因为它们为优化器/专用程序提供了额外的信息。 - smls
1
@Borodin - 这种类型的论点可能更适合在Software Engineering.SE上作为问答 :) - DVK
1
@Borodin 基于你在这里所说的,你似乎与 Larry Wall 有相同的观点,他是所有 Perl(包括 Perl 6)的首席设计师。Perl 1 到 6 非常注重保持弱类型,这背后有深刻的原因。DVK 的例子和所述的速度问题反映出对 Perl 6 类型的理解不足,这是可以理解的——那就是为什么他们在 SO 上提问。我希望明天有时间写个答案,但它基本上就是 @smls 所说的。虽然你们两个可能认为自己有分歧,但我认为你们可能没有。 - raiph
显示剩余4条评论
2个回答

5
最全面、设计良好的Perl 6性能测试工具是https://github.com/japhb/perl6-bench,但它并不专注于可选类型的相对性能。然而,它支持多个VM后端,所以这可能是一个很好的起点。

3
这个答案旨在补充@donaldh的答案,尽管我也不知道任何针对Perl 6类型/类型约束的特定研究。

静态类型检查很快

在下面的代码中,编译器会在编译时进行Complex~~Real类型检查:

sub mySub(Real $probability where 0 < * < 1) {
    # Do stuff with $probability
}

my Complex \number = 1-2e3i;

mySub number;

编译上述代码时,Rakudo编译器意识到numberComplex——因此您会得到一个编译时类型检查失败的错误提示:
===SORRY!=== Error while compiling

Calling mySub(Complex) will never work with declared signature (Real $probability)

随着时间推移,像Rakudo这样的Perl 6编译器可以改善它们的编译时代码分析,从而在编译时进行更多类型检查,如上所述。
请注意,甚至没有尝试使用“where”子句。仅仅指定“where”子句没有任何惩罚。由于“where”子句导致的任何开销只有在变量/值通过基本类型检查后才适用。
大多数动态类型检查也很快
在下面的示例中,编译器在运行时执行了Complex~~Real类型检查:
multi sub mySub(Real $probability where 0 < * < 1) {
    # Do stuff with $probability
}

multi sub mySub(Complex $probability) {
    # Do stuff with $probability
}

my \number = 1-2e3i;

mySub number;

编译上述代码时,Rakudo编译器目前不会在编译时意识到numberComplex。在运行时,它考虑并拒绝第一个multi sub声明。与之前相同,它甚至不尝试使用where子句。相反,它成功调用了第二个multi sub

迄今为止的例子应该清楚地表明,对于大多数类型检查来说,要么根本不需要,要么几乎没有运行时性能损失。

原生类型

理论上,可以使用原生类型以获得更好的性能:

sub foo-Int (Int $a) { my Int $b; $b++ for ^$a }
sub foo-int (int $a) { my int $b; $b++ for ^$a }

my $time;
$time = now; foo-Int my Int $ = 1e6.Int; say now - $time; # 1.0597491
$time = now; foo-int my int $ = 1e6.Int; say now - $time; # 0.7627174

在实践中,指定本地类型有时会减慢代码速度。
随着Rakudo的优化改进,相对于Int,int优化应变得更加一致和重要。其他本地标量类型和本地数组也适用类似的情况。
强制类型转换
Perl 6支持“强制类型转换”。
例如,Str(Int)接受任何Int或其子类型,并将其强制转换为Str。
如果强制类型转换的内部类型匹配,则编译器还将承担运行时开销来运行强制类型转换代码。
where子句
在完成上述常规静态和动态类型检查后,调用任何适用的where子句,直到其中一个成功或所有都失败。
编译器可以分析where子句并意识到它们等效于足够简单的静态类型表达式(例如where Int | Str),并使用此信息避免运行任意代码所带来的运行时开销。(参见@Larry's speculation about related matters。)目前的Rakudo不分析where子句。实际上,它比严格需要更频繁地调用where子句。
编译器/后端的性能是特定编译器/后端的函数。
截至2017年,主要的Perl 6编译器是Rakudo/MoarVM。过去曾有其他编译器编译了Perl 6的重要子集,未来也肯定会有;这些可能提供额外的数据。
“可选类型”与“渐进类型”
Perl 6引入了可选类型
如果您决定在网络上搜索相关数据...
Perl 6支持类继承和多分派等功能。根据维基百科的定义,这两个功能都在技术上使Perl 6无法拥有“可选类型”系统。
维基百科将Perl 6放在“渐进式类型”的广泛类别中,Larry Wall和doc.perl6.org也是如此。

2
请注意,不指定类型与指定 Any 类型是相同的(通常情况下)。因此,在这种情况下,您正在将值的类型检查与 Any 进行比较。不指定类型并不意味着 Rakudo 不进行类型检查:它一直在进行类型检查。因此,指定基本类型不会产生性能损失。如果子类型无法在编译时解析(例如 where 子句),则可能会引入性能损失。 - Elizabeth Mattijsen
纯属好奇,如果where子句和subtype的表达式是一样的,这两者是否匹配?例如概率的0 <* <=1,如果你从Real指定了相同方式的subtype,那么分发是否更快,无需进行where检查? - Matt Oates
@MattOates 你是在问是否将用户代码重构直接where子句(位于参数右侧)转换为通过子集类型(位于变量/参数左侧)的间接子句,可以提高运行时性能吗?如果是这样,根据我所知,答案是否定的。我目前对Larry的猜测的解释是编译器可以在编译时合理地发现where表达式将计算为命名静态类型。我想知道与多候选排序语义的相互作用 - raiph
@raiph 我的意思是,如果多重分派中的一个要求是一个 where 子句,但传递的类型是具有相同 where 子句的子类型,那么编译器是否已经静态地理解这些是相同的,并为子类型添加了一个规则,也将其分派到相同的方法。最终,您将知道 where 子句在 AST 中编译成什么,如果它们是相同的,则知道子类型明确与其他地方的约束相同,并且可以在不评估分派点处的检查的情况下匹配它。 - Matt Oates
@MattOates我已经彻底重写了我的答案。我认为你所问的与@Larry对相关事项的推测相似,正如我在我的改写中提到的那样。 - raiph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接