Czy ktoś zna odpowiedź i / lub ma na ten temat opinię?
Ponieważ krotki normalnie nie byłyby bardzo duże, przypuszczam, że bardziej sensowne byłoby użycie do nich struktur niż klas. Co powiesz?
Czy ktoś zna odpowiedź i / lub ma na ten temat opinię?
Ponieważ krotki normalnie nie byłyby bardzo duże, przypuszczam, że bardziej sensowne byłoby użycie do nich struktur niż klas. Co powiesz?
Odpowiedzi:
Firma Microsoft stworzyła wszystkie typy odwołań do typów krotek w celu uproszczenia.
Osobiście uważam, że to był błąd. Krotki z więcej niż 4 polami są bardzo nietypowe i i tak powinny zostać zastąpione bardziej typową alternatywą (taką jak typ rekordu w F #), więc tylko małe krotki są praktyczne. Moje własne testy porównawcze pokazały, że rozpakowane krotki do 512 bajtów mogą być nadal szybsze niż krotki w pudełkach.
Chociaż wydajność pamięci jest jednym z problemów, uważam, że dominującą kwestią jest obciążenie modułu odśmiecania pamięci .NET. Alokacja i zbieranie są bardzo kosztowne w .NET, ponieważ jego moduł odśmiecania pamięci nie został zbyt mocno zoptymalizowany (np. W porównaniu z maszyną JVM). Ponadto domyślny .NET GC (stacja robocza) nie został jeszcze zrównoleglony. W rezultacie równoległe programy, które używają krotek, zatrzymują się, ponieważ wszystkie rdzenie walczą o współdzielony moduł odśmiecania pamięci, niszcząc skalowalność. To nie tylko dominujący problem, ale, AFAIK, został całkowicie zaniedbany przez Microsoft, gdy badał ten problem.
Kolejnym problemem jest wirtualna wysyłka. Typy referencyjne obsługują podtypy, dlatego ich elementy członkowskie są zwykle wywoływane za pośrednictwem wirtualnej wysyłki. W przeciwieństwie do tego typy wartości nie mogą obsługiwać podtypów, więc wywołanie elementu członkowskiego jest całkowicie jednoznaczne i zawsze można je wykonać jako bezpośrednie wywołanie funkcji. Wirtualna wysyłka jest niezwykle kosztowna w przypadku nowoczesnego sprzętu, ponieważ procesor nie może przewidzieć, gdzie skończy się licznik programu. JVM dokłada wszelkich starań, aby zoptymalizować wysyłanie wirtualne, ale .NET tego nie robi. Jednak .NET zapewnia ucieczkę przed wirtualną wysyłką w postaci typów wartości. Zatem reprezentowanie krotek jako typów wartości mogłoby tutaj ponownie znacznie poprawić wydajność. Na przykład dzwonienieGetHashCode
na dwukrotnej krotce milion razy zajmuje 0,17 s, ale wywołanie jej na równoważnej strukturze zajmuje tylko 0,008 s, tj. typ wartości jest 20 razy szybszy niż typ referencyjny.
Prawdziwą sytuacją, w której często występują problemy z wydajnością krotek, jest użycie krotek jako kluczy w słownikach. Właściwie natknąłem się na ten wątek, podążając za linkiem z pytania przepełnienia stosu. F # uruchamia mój algorytm wolniej niż Python! gdzie autorski program F # okazał się wolniejszy niż jego Python właśnie dlatego, że używał krotek pudełkowych. Ręczne rozpakowywanie przy użyciu odręcznego struct
typu sprawia, że jego program F # jest kilka razy szybszy i szybszy niż Python. Te problemy nigdy by się nie pojawiły, gdyby krotki były reprezentowane przez typy wartości, a nie typy referencyjne na początku ...
Tuple<_,...,_>
typy mogły zostać przypieczętowane, w takim przypadku żadna wirtualna wysyłka nie byłaby potrzebna, mimo że są typami referencyjnymi. Bardziej ciekawi mnie, dlaczego nie są zapieczętowane, niż dlaczego są typami referencyjnymi.
Powodem jest najprawdopodobniej to, że tylko mniejsze krotki miałyby sens jako typy wartości, ponieważ miałyby niewielki ślad pamięci. Większe krotki (tj. Te z większą liczbą właściwości) faktycznie ucierpiałyby na wydajności, ponieważ byłyby większe niż 16 bajtów.
Zamiast tego, aby niektóre krotki były typami wartości, a inne były typami referencyjnymi, i zmuszają programistów, aby wiedzieli, które z nich są, jak sobie wyobrażam, ludzie w firmie Microsoft myśleli, że uczynienie ich wszystkich typów referencyjnych było prostsze.
Ach, podejrzenia się potwierdziły! Zobacz sekcję Budowanie :
Pierwsza ważna decyzja dotyczyła traktowania krotek jako referencji lub typu wartości. Ponieważ są one niezmienne za każdym razem, gdy chcesz zmienić wartości krotki, musisz utworzyć nową. Jeśli są to typy referencyjne, oznacza to, że może powstać wiele śmieci, jeśli zmieniasz elementy w krotce w ciasnej pętli. Krotki F # były typami referencyjnymi, ale zespół miał wrażenie, że mógłby uzyskać poprawę wydajności, gdyby zamiast tego dwie, a może trzy krotki elementów były typami wartości. Niektóre zespoły, które utworzyły wewnętrzne krotki, używały wartości zamiast typów referencyjnych, ponieważ ich scenariusze były bardzo wrażliwe na tworzenie wielu zarządzanych obiektów. Odkryli, że użycie typu wartości zapewniło im lepszą wydajność. W naszej pierwszej wersji roboczej specyfikacji krotki Zachowaliśmy dwu-, trzy- i czteroelementowe krotki jako typy wartości, a reszta to typy referencyjne. Jednak podczas spotkania projektowego, które obejmowało przedstawicieli innych języków, zdecydowano, że ten „podzielony” projekt będzie mylący ze względu na nieco inną semantykę obu typów. Stwierdzono, że spójność w zachowaniu i projekcie ma wyższy priorytet niż potencjalny wzrost wydajności. Na podstawie tych danych wejściowych zmieniliśmy projekt tak, aby wszystkie krotki były typami referencyjnymi, chociaż poprosiliśmy zespół F # o przeprowadzenie analizy wydajności, aby sprawdzić, czy wystąpiło przyspieszenie podczas używania typu wartości dla niektórych rozmiarów krotek. Miał dobry sposób na przetestowanie tego, ponieważ jego kompilator, napisany w języku F #, był dobrym przykładem dużego programu, który używał krotek w różnych scenariuszach. W końcu zespół F # stwierdził, że nie uzyskał poprawy wydajności, gdy niektóre krotki były typami wartości zamiast typów referencyjnych. To sprawiło, że poczuliśmy się lepiej, gdy zdecydowaliśmy się na użycie typów referencyjnych dla krotki.
Gdyby typy .NET System.Tuple <...> zostały zdefiniowane jako struktury, nie byłyby skalowalne. Na przykład trójskładnikowa krotka długich liczb całkowitych jest obecnie skalowana w następujący sposób:
type Tuple3 = System.Tuple<int64, int64, int64>
type Tuple33 = System.Tuple<Tuple3, Tuple3, Tuple3>
sizeof<Tuple3> // Gets 4
sizeof<Tuple33> // Gets 4
Gdyby trójskładnikowa krotka została zdefiniowana jako struktura, wynik byłby następujący (na podstawie przykładu testowego, który zaimplementowałem):
sizeof<Tuple3> // Would get 32
sizeof<Tuple33> // Would get 104
Ponieważ krotki mają wbudowaną obsługę składni w języku F # i są niezwykle często używane w tym języku, krotki „struct” narażałyby programistów F # na ryzyko pisania nieefektywnych programów, nawet o tym nie wiedząc. Stałoby się to tak łatwo:
let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3
Moim zdaniem krotki „strukturalne” spowodowałyby duże prawdopodobieństwo powstania znacznych nieefektywności w codziennym programowaniu. Z drugiej strony, istniejące obecnie krotki „klasowe” również powodują pewne nieefektywności, o czym wspomniał @Jon. Myślę jednak, że iloczyn „prawdopodobieństwa wystąpienia” razy „potencjalne uszkodzenie” byłby znacznie wyższy w przypadku struktur niż obecnie w przypadku klas. Dlatego obecne wdrożenie jest mniejszym złem.
Idealnie byłoby, gdyby istniały zarówno krotki „class”, jak i „struct”, obie z obsługą składni w języku F #!
Edycja (2017-10-07)
Krotki struktury są teraz w pełni obsługiwane w następujący sposób:
ref
, lub może nie lubić faktu, że tak zwane „niezmienne struktury” nie są, zwłaszcza gdy są opakowane. Szkoda, że .net nigdy nie zaimplementował koncepcji przekazywania parametrów przez element egzekwowalny const ref
, ponieważ w wielu przypadkach taka semantyka jest naprawdę wymagana.
Dictionary
, np. Tutaj: stackoverflow.com/questions/5850243 /…
W przypadku krotek 2 nadal można zawsze używać KeyValuePair <TKey, TValue> z wcześniejszych wersji systemu Common Type. To typ wartości.
Drobnym wyjaśnieniem artykułu Matta Ellisa byłoby to, że różnica w semantyce użycia między typami referencyjnymi i wartościowymi jest tylko „niewielka”, gdy obowiązuje niezmienność (co oczywiście miałoby miejsce w tym przypadku). Niemniej jednak myślę, że byłoby najlepiej w projekcie BCL nie wprowadzać zamieszania związanego z przejściem Tuple do typu odniesienia na pewnym progu.
Nie wiem, ale jeśli kiedykolwiek używałeś F # krotki są częścią języka. Jeśli utworzyłem plik .dll i zwróciłem typ krotek, byłoby miło mieć typ do umieszczenia go. Podejrzewam teraz, że F # jest częścią języka (.Net 4) wprowadzono pewne modyfikacje w CLR w celu dostosowania niektórych wspólnych struktur w F #
Z 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<...>
. Zobacz odniesienia w C # typy krotek