web-dev-qa-db-ja.com

なぜRakuは多次元配列でそれほどパフォーマンスが悪いのですか?

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

私は間違っていますか?違いが大きすぎるようです。

10
Javi

最初の直接比較

最初に、あなたの翻訳よりもあなたのPythonコードとより密接に整合しているコードから始めます。私は think 最も直接的に同等のRakuコードです)あなたのPythonは:

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

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

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

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


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

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

第二に、RakuコードとPythonコードの両方が、たとえばC#コードに比べて遅いです。

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

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

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

概念的な100億要素配列に対応するこのコードにもかかわらず、単なる1600万要素 PythonおよびC#コード内の1つ)実行のための壁時計時間は、Pythonコードの半分以下であり、C#コードよりも5倍遅いだけです。これは、RakudoがRakuコードを同等のPythonコード、およびC#コードの100倍の速さ。

テーブルはxx Infを使用して lazily で初期化されているため、Rakuコードは非常に高速に見えます。4 重要な作業は、say行を実行することだけです。これにより、100,000の最初の次元の配列が作成され、100,000の要素で100,000番目の2次元の配列が生成されるため、sayはその配列の最後の要素に保持されている0を表示できます。

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

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

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

未来

Rakuは非常に最適化された可能、つまり可能から 1日実行するように慎重に設計されています十分なコンパイラが今後数年で十分に機能する、たとえば、Perl 5またはPython 3は、理論的には、実行しない限り実行できます)ゼロからの再設計と対応するコンパイラー作業の長年。

やや大丈夫な類推は、過去25年間にJavaのパフォーマンスで何が起こったかです。 Rakudo/NQP/MoarVMは、Javaスタックが経験した成熟プロセスのほぼ半分です。

脚注

1 my $table := ...と書いてもよかった。ただし、my \foo ...という形式の宣言では、シギルの考慮が不要になり、=ではなく:=を使用できるようになります。 (おまけとして、「シギルをスラッシュアウトする」と、シギルを使用しない多くの言語のプログラマーに馴染みのあるシギルのない識別子が得られます。もちろん、PythonおよびC#が含まれます。)

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

 これを書いている時点では、 [〜#〜] tio [〜#〜] はPython 3.7.4、2019年6月以降、およびRakudo v2018.12を使用しています。 、2018年12月から。Rakudoのパフォーマンスは現在、公式のPython 3インタープリターよりも大幅に速く向上しています。したがって、Rakudoが遅い場合、最新のRakudoと最新のPythonのギャップが予想されます。 、この回答に記載されているよりも大幅に狭くなっています。特に、現在の作業では、割り当てのパフォーマンスが大幅に向上しています。

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

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

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

12
raiph