多次元配列でRakuのパフォーマンスが低下するのはなぜですか?


10

Rakuが多次元配列の操作をそれほどうまく実行できないのはなぜでしょうか。Python、C#、Rakuで2次元マトリックスを初期化する簡単なテストを行ったところ、経過時間が驚くほど長くなりました。

楽のために

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
成形多次元配列(たとえば、で定義するもの@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

驚くばかり!それは物事を簡単にします。
Scimon Proctor

2
形作られた多次元配列は、楽堂の他の多くの領域が受けた最適化の良さをまだ受けていません。
Elizabeth Mattijsen

回答:


13

最初の直接比較

まずは、独自の翻訳よりもPythonコードに非常に近いコードから始めます。私はあなたのPythonと最も直接同等のRakuコードは次のとおりだと思います:

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

このコードは、印のない識別子を宣言します1。それ:

  • 「シェーピング」(ドロップス[4000;4000]中をmy @table[4000;4000])。あなたのPythonコードがそれをしていないので、それを落としました。シェーピングは利点をもたらしますが、パフォーマンスに影響します。2

  • 割り当ての代わりにバインディングを使用します。Pythonコードが割り当てではなくバインディングを行っているため、バインディングに切り替えました。(Pythonはこの2つを区別しません。)Rakuの割り当てアプローチは、一般的なコードに持つ価値のある基本的な利点をもたらしますが、パフォーマンスに影響します。


私が私の答えを始めたこのコードはまだ遅いです。

まず、同じハードウェアで2018年12月からRakudoコンパイラーを介して実行されるRakuコードは、2019年6月からPythonインタープリターを使用すると、Pythonコードよりも約5倍遅くなります。

第2に、RakuコードとPythonコードの両方が、たとえばC#コードに比べて遅いです。もっと上手にできる...

1000倍速い慣用的な代替手段

次のコードは検討に値します:

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

このコードは、PythonおよびC#コードの単なる1,600万要素の配列ではなく、想定上の100億要素の配列に対応していますが、実行にかかる実時間は、Pythonコードの半分以下であり、C#よりも5倍遅いだけです。コード。これは、Rakudoが同等のPythonコードの1000倍、C#コードの100倍以上の速度でRakuコードを実行していることを示唆しています。

を使用してテーブルを遅延初期化しいるため、Rakuコードは非常に高速に見えますxx Inf4唯一の重要な作業は、sayラインの実行で行われます。これにより、100,000の最初の次元の配列が作成され、100,000の要素を持つ100,000番目の2次元の配列にデータが入力されるため、はその配列の最後の要素に保持されている要素をsay表示できます0

それを行うには複数の方法があります

あなたの質問の根底にある1つの問題は、それを行うには常に複数の方法があるということです。5速度が重要なコードのパフォーマンスが低下した場合、私が行ったように別の方法でコーディングすると、劇的な違いが生じる可能性があります。6

(別の本当に良い選択肢は、SOの質問をすることです...)

未来

Rakuは高度に最適化できるように注意深く設計されています。つまり、Perl 5またはPython 3が理論的には、地上を通過しない限り、実行できるよりも、今後数年間十分なコンパイラー作業が与えられれ1日ははるかに速く実行できます。再設計と対応するコンパイラー作業の年月。

ややわかりやすい例えは、過去25年間にJavaのパフォーマンスで起こったことです。Rakudo / NQP / MoarVMは、Javaスタックが経験した成熟プロセスのほぼ半分です。

脚注

1書けmy $table := ...た しかし、フォームの宣言はmy \foo ...シギルの考慮を排除し、シギル化された識別子で必要とされる=よりもむしろ使用を許可します:=。(おまけとして、「シギルを削減する」と、シギルのない識別子が得られます。これは、もちろんPythonとC#を含むシギルを使用しない多くの言語のプログラマーによく知られています。)

2シェーピングにより、一部のコードでは配列演算が高速になる場合があります。その間、質問へのコメントで述べたように、現在は明らかに逆のことを行っており、大幅に遅くなっています。今のところ、すべての配列へのアクセスが単純に動的に境界チェックされ、すべてがゆっくりとダウンしており、固定サイズを使用して高速化を図る努力もなされていないためです。さらに、コードの高速な回避策を考え出そうとしたとき、固定サイズの配列に対する多くの操作が現在実装されていないため、固定サイズの配列を使用するものを見つけることができませんでした。繰り返しになりますが、これらは1日のうちに実装されることが期待されますが、これまでの実装に取り​​組む誰にとっても、おそらく十分な課題ではありませんでした。

3これを書いている時点で、TIOは2019年6月にPython 3.7.4を使用し、2018年12月にRakudo v2018.12を使用しています。最新のRakudoと最新のPythonの間のギャップは、Rakudoが遅い場合、この回答で述べられているよりも大幅に狭くなると予想されます。特に、現在の作業では、割り当てのパフォーマンスが大幅に向上しています。

4の xxデフォルトは遅延処理ですが、一部の式では、言語のセマンティクスまたは現在のコンパイラーの制限により、熱心な評価が強制されます。1年前のv2018.12 Rakudoでは、フォームの表現を[ [ foo xx bar ] xx baz ]怠惰なままにし、熱心に評価することを強制されないようにするには、との両方 barであるbaz必要がありますInf。対照的に、my \table = [0 xx 100_000 for ^100_000]は使用せずに遅延しますInf。(後者のコードは実際には、100,000 Seq秒ではなく100,000 Array秒を最初の次元に格納していsay WHAT table[0]ます- ではSeqなく表示しますArrayが、ほとんどのコードは違いを見つけることができません- say table[99_999;99_999]引き続き表示され0ます)。

5一部の人々は、与えられた問題の解決策を考え、コード化する方法は複数あることを受け入れるのは弱点だと考えています。実際には、それは少なくとも3つの点で強みです。まず、一般的に、重要な問題はパフォーマンスプロファイルに劇的な違いがある多くの異なるアルゴリズムによって解決できます。この回答には、1年前のRakudoですでに利用可能なアプローチが含まれています。これは、一部のシナリオでは、実際にはPythonよりも1000倍以上高速です。第二に、Rakuのような意図的に柔軟でマルチパラダイム言語を使用すると、コーダー(またはコーダーのチーム)は、彼らがエレガントで保守しやすいと考えるソリューション、または彼らが何をしているか基づいて仕事を成し遂げるソリューションを表現できます言語が課すものではなく、最良であると考えます。第三に、最適化コンパイラとしてのRakudoのパフォーマンスは、現在、特に変動します。幸いなことに、プロファイラー6が優れているため、ボトルネックがどこにあるかを確認でき、柔軟性が高いため、別のコーディングを試すことができ、これにより非常に高速なコードが生成されます。

6パフォーマンスが重要な場合、またはパフォーマンスの問題を調査している場合は、パフォーマンスに関するRakuドキュメントページを参照してください。このページでは、Rakudoプロファイラーの使用を含むさまざまなオプションについて説明しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.