誰かが答えを知っているか、これについて意見を持っていますか?
タプルは通常それほど大きくないので、これらよりもクラスよりも構造体を使用する方が理にかなっていると思います。何て言うの?
回答:
マイクロソフトは、単純化のために、すべてのタプルタイプを参照タイプにしました。
個人的にこれは間違いだったと思います。4つを超えるフィールドを持つタプルは非常に珍しいものであり、とにかくよりタイプフルな代替(F#のレコードタイプなど)に置き換える必要があるため、小さなタプルのみが実用的に重要です。私自身のベンチマークは、512バイトまでのボックス化されていないタプルは、ボックス化されたタプルよりも高速である可能性があることを示しました。
メモリ効率は1つの懸念事項ですが、主な問題は.NETガベージコレクタのオーバーヘッドにあると思います。ガベージコレクタは(JVMと比較して)あまり最適化されていないため、.NETでの割り当てとコレクションは非常に高価です。さらに、デフォルトの.NET GC(ワークステーション)はまだ並列化されていません。その結果、タプルを使用する並列プログラムは、すべてのコアが共有のガベージコレクターをめぐって争い、スケーラビリティを破壊するので、停止します。これは主要な懸念事項であるだけでなく、AFAIKは、Microsoftがこの問題を調査したときに完全に無視されました。
もう1つの懸念は、仮想ディスパッチです。参照型はサブタイプをサポートするため、それらのメンバーは通常、仮想ディスパッチを介して呼び出されます。対照的に、値型はサブタイプをサポートできないため、メンバーの呼び出しは完全に明確であり、常に直接関数呼び出しとして実行できます。CPUがプログラムカウンターが終了する場所を予測できないため、仮想ディスパッチは最新のハードウェアでは非常に高価です。JVMは仮想ディスパッチを最適化するために非常に長くなりますが、.NETはそうではありません。ただし、.NETは、値型の形式で仮想ディスパッチからの脱出を提供します。したがって、タプルを値型として表すと、ここでもパフォーマンスが劇的に向上する可能性があります。たとえば、GetHashCode
2タプルで100万回かかると0.17秒かかりますが、同等の構造体で呼び出すと0.008秒しかかかりません。つまり、値の型は参照型よりも20倍高速です。
タプルに関するこれらのパフォーマンスの問題が一般的に発生する実際の状況は、タプルを辞書のキーとして使用する場合です。Stack Overflowの質問のリンクをたどってこのスレッドに実際に遭遇しました。F#は私のアルゴリズムをPythonよりも低速で実行します。作者のF#プログラムは、ボックス化されたタプルを使用していたため、Pythonよりも遅いことがわかりました。手書きのstruct
型を使用して手動でボックス化を解除すると、彼のF#プログラムがPythonより数倍速く、高速になります。これらの問題は、タプルが値の型で表され、最初に参照型ではない場合には発生しませんでした...
Tuple<_,...,_>
されていると思います。型は封印されている可能性があり、その場合、参照型であっても仮想ディスパッチは必要ありません。それらが参照型である理由よりも、なぜそれらがシールされないのかについて私はもっと興味があります。
その理由は、メモリフットプリントが小さいため、値の型として意味があるのは小さなタプルだけだからです。より大きなタプル(つまり、より多くのプロパティを持つタプル)は、16バイトよりも大きいため、実際にはパフォーマンスが低下します。
いくつかのタプルを値の型にして、他のタプルを参照の型にして、開発者にどれがどれであるかを知ってもらうよりも、Microsoftの人たちはすべての参照の型を作る方が簡単だと思ったと思います。
ああ、疑惑が確認されました!タプルの構築を参照してください:
最初の主要な決定は、タプルを参照タイプと値タイプのどちらとして扱うかでした。タプルの値を変更したいときはいつでも不変なので、新しいタプルを作成する必要があります。それらが参照型である場合、これは、タイトループでタプルの要素を変更している場合、大量のガベージが生成される可能性があることを意味します。F#のタプルは参照型でしたが、2つ、場合によっては3つの要素タプルを値型にすると、パフォーマンスの向上を実現できるとチームから感じていました。内部タプルを作成した一部のチームは、参照タイプではなく値を使用していました。彼らのシナリオは、多数の管理対象オブジェクトの作成に非常に敏感だからです。値型を使用すると、パフォーマンスが向上することがわかりました。タプル仕様の最初のドラフトでは、2要素、3要素、および4要素のタプルを値の型として保持し、残りは参照型です。ただし、他の言語の代表者を含む設計会議中に、2つのタイプのセマンティクスがわずかに異なるため、この「分割」設計は混乱を招くと判断されました。動作と設計の一貫性は、潜在的なパフォーマンスの向上よりも優先されると判断されました。この入力に基づいて、すべてのタプルが参照タイプになるように設計を変更しましたが、いくつかのサイズのタプルに値タイプを使用するときにスピードアップが発生するかどうかを確認するためにF#チームにパフォーマンス調査を依頼しました。これをテストする良い方法がありました。それは、F#で書かれたコンパイラーが、さまざまなシナリオでタプルを使用する大きなプログラムの良い例でした。最終的に、F#チームは、一部のタプルが参照型ではなく値型である場合、パフォーマンスが向上しないことを発見しました。これにより、タプルに参照型を使用するという決定について、私たちは気分が良くなりました。
.NET System.Tuple <...>型が構造体として定義されている場合、それらはスケーラブルではありません。たとえば、長整数の三項タプルは現在次のようにスケーリングされます。
type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4
三項タプルが構造体として定義されている場合、結果は次のようになります(私が実装したテスト例に基づく)。
sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104
タプルはF#に組み込みの構文サポートを備えており、この言語で非常に頻繁に使用されるため、「構造体」タプルは、F#プログラマーに気付かれずに非効率なプログラムを作成するリスクをもたらします。それはとても簡単に起こります:
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3
私の意見では、「構造体」タプルは、日常のプログラミングで重大な非効率を生み出す可能性が高くなります。一方、@ Jonが言及しているように、現在存在する「クラス」タプルも特定の非効率を引き起こします。ただし、「発生確率」と「潜在的な損傷」の積は、現在のクラスよりも構造体の方がはるかに高いと思います。したがって、現在の実装はそれほど悪ではありません。
理想的には、「クラス」タプルと「構造体」タプルの両方があり、どちらもF#で構文がサポートされています。
編集(2017-10-07)
構造タプルは、次のように完全にサポートされるようになりました。
ref
、構造体をで渡すという考えにうまく対応していないか、いわゆる「不変の構造体」が特にボックス化されている場合はそうでないという事実を好まない可能性があります。const ref
多くの場合、そのようなセマンティクスが実際に必要なものであるため、.netが実施可能なによってパラメーターを渡すという概念を実装していないのは残念です。
Dictionary
例:stackoverflow.com/questions/5850243) /…
2タプルの場合でも、以前のバージョンのCommon Type SystemのKeyValuePair <TKey、TValue>を常に使用できます。値型です。
Matt Ellisの記事を少し説明すると、参照と値の型の間の使用セマンティクスの違いは、不変性が有効な場合にのみ「ごくわずか」になることです(もちろん、ここでも同様です)。それでも、BCL設計では、タプルがあるしきい値で参照型にクロスオーバーするという混乱を招かないことが最善であったと思います。
わかりませんが、F#タプルを使用したことがある場合は、言語の一部です。.dllを作成してタプルのタイプを返した場合、それを入れるタイプがあると便利です。F#が言語の一部である(.Net 4)ので、CLRにいくつかの変更が加えられ、一般的な構造に対応しましたF#で
http://en.wikibooks.org/wiki/F_Sharp_Programming/Tuples_and_Recordsから
let scalarMultiply (s : float) (a, b, c) = (a * s, b * s, c * s);;
val scalarMultiply : float -> float * float * float -> float * float * float
scalarMultiply 5.0 (6.0, 10.0, 20.0);;
val it : float * float * float = (30.0, 50.0, 100.0)
ValueTuple<...>
です。C#タプルタイプの