为什么Raku在多维数组上表现不佳?


10

我很好奇,为什么Raku在处理多维数组方面表现不佳。我进行了快速测试,使用Python,C#和Raku初始化了二维矩阵,而后面的耗时惊人地长。

对于乐

my @grid[4000;4000] = [[0 xx 4000] xx 4000];
# Elapsed time 42 seconds !!

对于Python

table= [ [ 0 for i in range(4000) ] for j in range(4000) ]
# Elapsed time 0.51 seconds

C#

int [,]matrix = new int[4000,4000];
//Just for mimic same behaviour
for(int i=0;i<4000;i++)
   for(int j=0;j<4000;j++)
       matrix[i,j] = 0;
# Elapsed time 0.096 seconds

我做错了吗?似乎相差太大。


5
对于形状多维数组(例如在EG上定义的数组)来说,这只是很慢,@grid[4000;4000]而python代码未使用形状数组,并且在Raku中尝试相同的代码后,您可以获得更好的返回时间:my @grid = [[0 xx 4000] xx 4000]; 这确实意味着您必须使用@grid[0][0]@grid[0;0]。我认为这主要是因为形状阵列仍在进行中。
Scimon Proctor,

1
在我的机器上@grid[1000;1000] = [[0 xx 1000]xx1000]花了12秒。@grid = [[0 xx 1000]xx1000]花了0.6所以...是的。我会避免使用整形数组。
Scimon Proctor,

5
@Scimon您实际上仍然可以将[;]访问器用于未整形的数组。 my @grid = [[$++ xx 100] xx 100]; say @grid[0;1]; say @grid[1;1]分别返回1和101
user0721090601 '19

太棒了!这使事情变得容易。
Scimon Proctor

2
成形的多维数组尚未获得Rakudo的许多其他区域所带来的优化优势。
伊丽莎白·马蒂森

Answers:


13

初步直接比较

我将从与您的Python代码比您自己的翻译更紧密地对齐的代码开始。我认为与您的Python最直接等效的Raku代码是:

my \table = [ [ 0 for ^4000 ] for ^4000 ];
say table[3999;3999]; # 0

该代码声明了无sigil的标识符1。它:

  • 删除“整形”([4000;4000]中的my @table[4000;4000])。我删除了它,因为您的Python代码没有执行该操作。塑造具有优势,但对性能有影响。2

  • 使用绑定而不是赋值。我切换到绑定是因为您的Python代码正在执行绑定,而不是分配。(Python不会在两者之间进行区分。)尽管Raku的赋值方法带来了一些普通代码值得拥有的基本优势,但它却对性能产生了影响。3


我开始回答的这段代码仍然很慢。

首先,从2018年12月开始通过Rakudo编译器运行的Raku代码比使用2019年6月起在相同硬件上使用Python解释器的Python代码慢大约5倍。3

其次,Raku代码和Python代码都很慢,例如,与您的C#代码相比。我们可以做得更好...

惯用的替代方法快一千倍

以下代码值得考虑:

my \table = [ [ 0 xx Inf ] xx Inf ];
say table[ 100_000; 100_000 ]; # 0

尽管此代码对应于名义上的100亿个元素数组,而不是Python和C#代码中仅有的1600万元素数组,但运行它的挂钟时间不到Python代码的一半,并且仅比C#慢5倍码。这表明Rakudo运行Raku代码的速度是等效的Python代码的一千倍,是C#代码的一百倍。

Raku代码似乎快得多,因为通过使用懒惰地初始化了表xx Inf4唯一重要的工作就是运行say生产线。这将导致创建100,000个第一维数组,然后仅在100,000个第二维数组中填充100,000个元素,以便say可以显示该0数组的最后一个元素。

有不止一种方法可以做到

问题背后的一个问题是,总有不止一种方法可以做到这一点。5如果对于速度至关重要的代码,您遇到的性能很差,那么像我所做的那样,对它进行不同的编码可能会产生巨大的变化。6

(另一个非常好的选择是问一个SO问题...)

未来

乐都经过精心设计,以高度optimiz ,即能够1天运行得更快给予足够的编译工作,未来数年,比譬如Perl 5或Python 3就可以了,在理论上不断运行,除非他们经过地面重新设计和多年的相应编译器工作。

在过去的25年中,Java的性能发生了一个比较好的类比。Rakudo / NQP / MoarVM大约已经完成了Java堆栈已经成熟的过程的一半。

脚注

1我本来可以写的my $table := ...。但是形式的声明my \foo ...消除了对信号的考虑,并允许使用=而不是:=带有sigil'd标识符的信息。(此外,“削减标记”会产生无标记标识符,这是许多不使用sigils的语言(当然包括Python和C#)的编码人员所熟悉的。)

2整天可能会导致某些代码的数组操作更快。同时,如对您的问题的评论中所述,当前它显然正在采取相反的行动,从而大大减慢了速度。我想这在很大程度上是因为目前对每个数组访问都进行了天真的动态边界检查,然后慢慢降低了所有内容,而且也没有努力使用固定大小来帮助加快处理速度。另外,当我尝试为您的代码提供一种快速的解决方法时,由于当前未实现对固定大小数组的许多操作,因此无法找到使用固定大小数组的代码。同样,希望有一天能够实施这些措施,但迄今为止,对于任何人来说,实施这些措施都不是一个足够的痛苦。

3在撰写本文时,TIO使用的是Python 3.7.4(从2019年6月开始)和Rakudo v2018.12(从2018年12月开始)。随着时间的推移,Rakudo的性能提高的速度明显快于正式的Python 3解释器,所以我会期望当Rakudo较慢时,最新的Rakudo与最新的Python之间的差距比此答案中所述的要窄得多。特别是,当前的工作正在显着提高分配的绩效。

4 xx默认为延迟处理,但是由于语言语义或当前编译器限制,某些表达式会强制进行急切评估。在该岁v2018.12 Rakudo,对形式的表达[ [ foo xx bar ] xx baz ],以保持懒惰和不被强迫急切地评估, barbaz必须Inf。相反,my \table = [0 xx 100_000 for ^100_000]懒惰而不使用Inf。(后面的代码实际上Seq在第一个维度上存储100,000 s,而不是100,000 Arrays- say WHAT table[0]显示Seq而不是Array-但大多数代码仍无法发现差异- say table[99_999;99_999]仍会显示0。)

5有些人认为接受针对问题的解决方案并思考的方法不止一种,这是一个缺点。实际上,它至少在三个方面具有优势。首先,一般而言,任何给定的非平凡问题都可以通过许多不同的算法来解决,这些算法在性能方面存在巨大差异。这个答案包括一岁的Rakudo已经可用的方法,在某些情况下,它实际上比Python快一千倍。其次,像乐故意灵活和多范式语言允许编码器(或编码器的团队)来表达一个解决方案,他们认为优雅和维护,或者只是能够完成任务,依据是什么,他们认为最好,而不是语言所强加的含义。第三,Rakudo作为优化编译器的性能目前变化很大。幸运的是,它具有出色的分析器6,因此可以看到瓶颈在哪里,并且具有很大的灵活性,因此可以尝试替代编码,这可能会产生速度大大提高的代码。

6当性能很重要时,或者如果您正在调查性能问题,请参阅Raku doc页面有关性能的信息;该页面涵盖了一系列选项,包括使用Rakudo Profiler。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.