1. ホーム
  2. performance

[解決済み] .Net 4.0の新しいTuple型は、なぜ参照型(クラス)であり、値型(構造体)ではないのでしょうか?

2023-04-14 16:30:18

疑問点

どなたか、この件に関する答えや意見をお持ちの方はいらっしゃいませんか?

タプルは通常あまり大きくないので、私はこれらのためにクラスよりも構造体を使用する方がより理にかなっていると仮定します。あなたはどう思いますか?

どのように解決するのですか?

Microsoft は、単純化のためにすべてのタプル型を参照型にしました。

私は個人的にこれは間違いであったと思います。4 つ以上のフィールドを持つタプルは非常に珍しく、いずれにせよより型付きな代替品 (F# のレコード型など) に置き換えられるべきで、小さなタプルのみが実用的な関心事となります。私自身のベンチマークでは、512バイトまでの非ボックス化タプルは、ボックス化タプルよりも高速であることが示されました。

メモリ効率は 1 つの懸念事項ですが、私は支配的な問題は .NET ガーベッジコレクターのオーバーヘッドであると信じています。アロケーションとコレクションは 非常に高価 そのガベージコレクターは (たとえば JVM と比較して) あまり大きく最適化されていないため、.NET 上での割り当てと収集は非常に高価です。さらに、デフォルトの .NET GC (ワークステーション) は、まだ並列化されていません。その結果、タプルを使用する並列プログラムは、すべてのコアが共有ガベージコレクタのために競合するため、スケーラビリティを破壊して停止します。これは支配的な懸念であるだけでなく、AFAIK では、Microsoft がこの問題を検討したときに完全に無視されました。

もうひとつの懸念は、仮想ディスパッチです。参照型はサブタイプをサポートし、したがって、そのメンバーは通常、仮想ディスパッチによって呼び出されます。対照的に、値型はサブタイプをサポートしないので、メンバーの呼び出しは完全に曖昧でなく、常に直接の関数呼び出しとして実行されることができます。仮想ディスパッチは、CPUがプログラム・カウンターの行き先を予測できないため、最近のハードウェアでは非常に高価です。JVMは仮想ディスパッチを最適化するために多大な努力を払いますが、.NETはそうではありません。しかし、.NETは、値型という形で仮想ディスパッチからの脱出を提供しています。したがって、タプルを値型として表現することで、ここでも劇的に性能を向上させることができたのです。例えば GetHashCode を100万回呼び出すと0.17秒かかりますが、同等の構造体で呼び出すとわずか0.008秒しかかかりません。

タプルのこれらのパフォーマンスの問題が一般的に発生する実際の状況は、辞書のキーとしてタプルを使用することです。私は実際に Stack Overflow の質問からのリンクをたどって、このスレッドに行き当たりました。 F#は私のアルゴリズムをPythonより遅く実行します! ここで、著者のF#プログラムは、まさにボックス化されたタプルを使用していたため、Pythonよりも遅いことが判明しました。手動でボックス化を解除するには、手書きの struct 型を使って手動でアンボックスすることで、彼のF#プログラムは数倍速くなり、Pythonよりも速くなりました。もしタプルが参照型ではなく値型によって表現されていれば、これらの問題は発生しなかったでしょう...。