Kennt jemand die Antwort und / oder hat er eine Meinung dazu?
Da Tupel normalerweise nicht sehr groß sind, würde ich annehmen, dass es sinnvoller wäre, für diese Strukturen als Klassen zu verwenden. Was sagst du?
Kennt jemand die Antwort und / oder hat er eine Meinung dazu?
Da Tupel normalerweise nicht sehr groß sind, würde ich annehmen, dass es sinnvoller wäre, für diese Strukturen als Klassen zu verwenden. Was sagst du?
Antworten:
Microsoft hat aus Gründen der Einfachheit alle Tupeltypen als Referenztypen festgelegt.
Ich persönlich halte das für einen Fehler. Tupel mit mehr als 4 Feldern sind sehr ungewöhnlich und sollten ohnehin durch eine typvollere Alternative ersetzt werden (z. B. ein Datensatztyp in F #), sodass nur kleine Tupel von praktischem Interesse sind. Meine eigenen Benchmarks haben gezeigt, dass Tupel ohne Box bis zu 512 Byte immer noch schneller sein können als Tupel mit Box.
Obwohl die Speichereffizienz ein Problem darstellt, glaube ich, dass das Hauptproblem der Overhead des .NET-Garbage-Collectors ist. Zuweisung und Erfassung sind in .NET sehr teuer, da der Garbage Collector nicht sehr stark optimiert wurde (z. B. im Vergleich zur JVM). Darüber hinaus wurde die Standard-.NET-GC (Workstation) noch nicht parallelisiert. Folglich kommen parallele Programme, die Tupel verwenden, zum Stillstand, da alle Kerne um den gemeinsam genutzten Garbage Collector kämpfen, wodurch die Skalierbarkeit zerstört wird. Dies ist nicht nur das Hauptanliegen, sondern AFAIK wurde von Microsoft bei der Untersuchung dieses Problems völlig vernachlässigt.
Ein weiteres Problem ist der virtuelle Versand. Referenztypen unterstützen Untertypen und daher werden ihre Mitglieder normalerweise über den virtuellen Versand aufgerufen. Im Gegensatz dazu können Werttypen keine Untertypen unterstützen, sodass der Aufruf von Mitgliedern völlig eindeutig ist und immer als direkter Funktionsaufruf ausgeführt werden kann. Der virtuelle Versand ist auf moderner Hardware sehr teuer, da die CPU nicht vorhersagen kann, wo der Programmzähler landen wird. Die JVM unternimmt große Anstrengungen, um den virtuellen Versand zu optimieren, .NET jedoch nicht. .NET bietet jedoch eine Flucht vor dem virtuellen Versand in Form von Werttypen. Die Darstellung von Tupeln als Werttypen hätte die Leistung hier wiederum dramatisch verbessern können. Zum Beispiel anrufenGetHashCode
Bei einem 2-Tupel dauert eine Million Mal 0,17 Sekunden, beim Aufrufen einer äquivalenten Struktur jedoch nur 0,008 Sekunden, dh der Werttyp ist 20-mal schneller als der Referenztyp.
Eine reale Situation, in der diese Leistungsprobleme bei Tupeln häufig auftreten, ist die Verwendung von Tupeln als Schlüssel in Wörterbüchern. Ich bin tatsächlich auf diesen Thread gestoßen, indem ich einem Link aus der Frage zum Stapelüberlauf gefolgt bin. F # führt meinen Algorithmus langsamer als Python aus! wo sich herausstellte, dass das F # -Programm des Autors langsamer war als sein Python, gerade weil er Boxed-Tupel verwendete. Durch manuelles Entpacken mit einem handgeschriebenen struct
Typ wird sein F # -Programm um ein Vielfaches schneller und schneller als Python. Diese Probleme wären niemals aufgetreten, wenn Tupel zunächst durch Werttypen und nicht durch Referenztypen dargestellt worden wären ...
Tuple<_,...,_>
Typen könnten versiegelt worden sein. In diesem Fall wäre kein virtueller Versand erforderlich, obwohl es sich um Referenztypen handelt. Ich bin neugieriger, warum sie nicht versiegelt sind, als warum sie Referenztypen sind.
Der Grund ist höchstwahrscheinlich, dass nur die kleineren Tupel als Werttypen sinnvoll wären, da sie einen geringen Speicherbedarf hätten. Die größeren Tupel (dh diejenigen mit mehr Eigenschaften) würden tatsächlich an Leistung verlieren, da sie größer als 16 Bytes wären.
Anstatt einige Tupel Werttypen und andere Referenztypen zu sein, müssen Entwickler wissen, welche es sind. Ich würde mir vorstellen, dass die Leute bei Microsoft es für einfacher hielten, sie alle Referenztypen zu erstellen.
Ah, Verdacht bestätigt! Siehe Building Tuple :
Die erste wichtige Entscheidung war, ob Tupel entweder als Referenz- oder als Werttyp behandelt werden sollen. Da sie jedes Mal unveränderlich sind, wenn Sie die Werte eines Tupels ändern möchten, müssen Sie einen neuen erstellen. Wenn es sich um Referenztypen handelt, bedeutet dies, dass viel Müll generiert werden kann, wenn Sie Elemente in einem Tupel in einer engen Schleife ändern. F # -Tupel waren Referenztypen, aber das Team hatte das Gefühl, dass sie eine Leistungsverbesserung erzielen könnten, wenn stattdessen zwei und vielleicht drei Elementtupel Werttypen wären. Einige Teams, die interne Tupel erstellt hatten, verwendeten Wert anstelle von Referenztypen, da ihre Szenarien sehr empfindlich auf die Erstellung vieler verwalteter Objekte reagierten. Sie fanden heraus, dass die Verwendung eines Wertetyps zu einer besseren Leistung führte. In unserem ersten Entwurf der Tupelspezifikation Wir haben die Tupel mit zwei, drei und vier Elementen als Werttypen beibehalten, der Rest sind Referenztypen. Während eines Designtreffens, an dem Vertreter anderer Sprachen teilnahmen, wurde jedoch entschieden, dass dieses "geteilte" Design aufgrund der leicht unterschiedlichen Semantik zwischen den beiden Typen verwirrend sein würde. Es wurde festgestellt, dass Konsistenz in Verhalten und Design eine höhere Priorität hat als potenzielle Leistungssteigerungen. Basierend auf dieser Eingabe haben wir das Design so geändert, dass alle Tupel Referenztypen sind, obwohl wir das F # -Team gebeten haben, eine Leistungsuntersuchung durchzuführen, um festzustellen, ob bei Verwendung eines Wertetyps für einige Tupelgrößen eine Beschleunigung aufgetreten ist. Es hatte eine gute Möglichkeit, dies zu testen, da sein Compiler, geschrieben in F #, war ein gutes Beispiel für ein großes Programm, das Tupel in verschiedenen Szenarien verwendete. Am Ende stellte das F # -Team fest, dass es keine Leistungsverbesserung erzielte, wenn einige Tupel Werttypen anstelle von Referenztypen waren. Dadurch fühlten wir uns besser bei unserer Entscheidung, Referenztypen für Tupel zu verwenden.
Wenn die .NET System.Tuple <...> -Typen als Strukturen definiert wären, wären sie nicht skalierbar. Beispielsweise skaliert ein ternäres Tupel langer Ganzzahlen derzeit wie folgt:
type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4
Wenn das ternäre Tupel als Struktur definiert wäre, wäre das Ergebnis wie folgt (basierend auf einem von mir implementierten Testbeispiel):
sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104
Da Tupel eine integrierte Syntaxunterstützung in F # haben und in dieser Sprache extrem häufig verwendet werden, besteht für "struct" -Tupel bei F # -Programmierern das Risiko, ineffiziente Programme zu schreiben, ohne sich dessen überhaupt bewusst zu sein. Es würde so leicht passieren:
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3
Meiner Meinung nach würden "Struktur" -Tupel mit hoher Wahrscheinlichkeit zu erheblichen Ineffizienzen bei der täglichen Programmierung führen. Andererseits verursachen die derzeit vorhandenen "Klassen" -Tupel auch bestimmte Ineffizienzen, wie von @Jon erwähnt. Ich denke jedoch, dass das Produkt aus "Eintrittswahrscheinlichkeit" mal "potenziellem Schaden" bei Strukturen viel höher wäre als derzeit bei Klassen. Daher ist die aktuelle Implementierung das geringere Übel.
Idealerweise gibt es sowohl "Klassen" -Tupel als auch "Struktur" -Tupel, beide mit syntaktischer Unterstützung in F #!
Bearbeiten (2017-10-07)
Strukturtupel werden jetzt wie folgt vollständig unterstützt:
ref
, oder die Tatsache nicht mag, dass sogenannte "unveränderliche Strukturen" dies nicht tun, insbesondere wenn sie in einer Box enthalten sind. Es ist schade, dass .net das Konzept der Übergabe von Parametern durch einen Durchsetzbaren nie implementiert hat const ref
, da in vielen Fällen eine solche Semantik wirklich erforderlich ist.
Dictionary
, z. B. hier: stackoverflow.com/questions/5850243 /…
Für 2-Tupel können Sie immer noch das KeyValuePair <TKey, TValue> aus früheren Versionen des Common Type Systems verwenden. Es ist ein Werttyp.
Eine kleine Klarstellung des Artikels von Matt Ellis wäre, dass der Unterschied in der Verwendungssemantik zwischen Referenz- und Werttypen nur "geringfügig" ist, wenn die Unveränderlichkeit wirksam ist (was hier natürlich der Fall wäre). Dennoch denke ich, dass es im BCL-Design am besten gewesen wäre, nicht die Verwirrung darüber einzuführen, dass Tuple an einem bestimmten Schwellenwert auf einen Referenztyp übergeht.
Ich weiß nicht, aber ob Sie jemals F # -Tupel verwendet haben, sind Teil der Sprache. Wenn ich eine DLL erstellt und einen Typ von Tupeln zurückgegeben habe, wäre es schön, einen Typ zu haben, in den ich das einfügen kann. Ich vermute jetzt, dass F # Teil der Sprache ist (.Net 4). Einige Änderungen an CLR wurden vorgenommen, um einige gemeinsame Strukturen zu berücksichtigen in F #
Von 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<...>
. Siehe Referenz bei C #