Хтось знає відповідь та / або має думку щодо цього?
Оскільки кортежі, як правило, не дуже великі, я вважаю, що для них буде більше сенсу використовувати структури, ніж класи. Що ти кажеш?
Хтось знає відповідь та / або має думку щодо цього?
Оскільки кортежі, як правило, не дуже великі, я вважаю, що для них буде більше сенсу використовувати структури, ніж класи. Що ти кажеш?
Відповіді:
Microsoft зробила всі типи кортежів посилальними типами в інтересах простоти.
Я особисто вважаю, що це була помилка. Кортежі з більш ніж 4 полями є дуже незвичними і в будь-якому випадку їх слід замінити на більш типову альтернативу (наприклад, тип запису у F #), тому лише невеликі кортежі представляють практичний інтерес. Мої власні тести показали, що кортежі без упаковки розміром до 512 байт все одно можуть бути швидшими, ніж кортежі в коробках.
Хоча ефективність пам'яті є однією з проблем, я вважаю, що домінуючою проблемою є накладні витрати на збирач сміття .NET. Розподіл та збір є дуже дорогими в .NET, оскільки його збирач сміття не був дуже сильно оптимізований (наприклад, порівняно з JVM). Більше того, .NET GC (робоча станція) за замовчуванням ще не паралелізований. Отже, паралельні програми, що використовують кортежі, зупиняються, оскільки всі ядра борються за спільний збирач сміття, руйнуючи масштабованість. Це не лише домінуюча проблема, але AFAIK повністю знехтувала Microsoft, коли вивчала цю проблему.
Іншим занепокоєнням є віртуальна відправка. Типи посилань підтримують підтипи, і, отже, їх члени, як правило, викликаються за допомогою віртуальної відправки. На відміну від цього, типи значень не можуть підтримувати підтипи, тому виклик члена є цілком однозначним і завжди може виконуватися як прямий виклик функції. Віртуальна відправка надзвичайно дорога для сучасного обладнання, оскільки центральний процесор не може передбачити, куди потрапить лічильник програм. JVM докладає всіх зусиль для оптимізації віртуальної відправки, але .NET цього не робить. Однак .NET надає можливість виходу з віртуальної відправки у вигляді типів значень. Отже, представлення кортежів як типів значень могло б, знову ж таки, значно покращити продуктивність тут. Наприклад, дзвінокGetHashCode
на 2-кортеж мільйон разів займає 0,17 с, але виклик його на еквівалентній структурі займає лише 0,008 с, тобто тип значення в 20 разів швидший за еталонний тип.
Реальна ситуація, коли ці проблеми з продуктивністю зазвичай виникають, полягає у використанні кортежів як ключів у словниках. Я насправді натрапив на цю тему, перейшовши за посиланням із запитання переповнення стека F #, запускає мій алгоритм повільніше, ніж Python! де авторська програма F # виявилася повільнішою за його Python саме тому, що він використовував кортежі в коробках. Розпакування вручну за допомогою рукописного struct
типу робить його програму F # в кілька разів швидшою та швидшою, ніж Python. Ці проблеми ніколи не виникали б, якби кортежі були представлені типами значень, а не посилальними типами для початку ...
Tuple<_,...,_>
типи могли бути запечатані, і в такому випадку ніяка віртуальна відправка не потрібна, незважаючи на те, що це посилальні типи. Мені цікавіше, чому вони не запечатані, ніж чому вони є еталонними типами.
Причина, швидше за все, тому, що лише менші кортежі мали б сенс як типи значень, оскільки вони мали б невеликий розмір пам'яті. Більші кортежі (тобто ті, що мають більше властивостей) насправді потерпають у продуктивності, оскільки вони будуть більшими за 16 байт.
Замість того, щоб деякі кортежі були типами значень, а інші - типовими посиланнями, і змушували розробників знати, які саме, я вважаю, люди в Microsoft думали, що зробити їх усі посилальні типи простішими.
Ах, підозри підтверджені! Будь ласка, дивіться Будівля Кортежу :
Перше головне рішення полягало в тому, чи слід розглядати кортежі як еталон або тип значення. Оскільки вони незмінні будь-коли, коли ви хочете змінити значення кортежу, вам доведеться створити нове. Якщо вони є еталонними типами, це означає, що може створюватися багато сміття, якщо ви змінюєте елементи кортежем в щільному циклі. Кортежі F # були еталонними типами, але команда відчувала, що вони можуть реалізувати покращення продуктивності, якщо два, а можливо, три кортежі елементів є типами цінностей. Деякі команди, які створили внутрішні кортежі, використовували значення замість посилальних типів, оскільки їх сценарії були дуже чутливими до створення великої кількості керованих об'єктів. Вони виявили, що використання типу значення дає їм кращу продуктивність. У нашому першому проекті специфікації кортежу, ми зберегли дво-, три- та чотириелементні кортежі як типи значень, а решта були еталонними типами. Однак під час зустрічі з дизайнерами, в якій взяли участь представники інших мов, було вирішено, що цей "роздвоєний" дизайн буде заплутаним через дещо різну семантику між двома типами. Визначено, що послідовність у поведінці та дизайні має вищий пріоритет, ніж потенційне підвищення продуктивності. На основі цього введення ми змінили дизайн таким чином, що всі кортежі є еталонними типами, хоча ми попросили команду 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 #!
Редагувати (07.10.2017)
Набори структур тепер повністю підтримуються наступним чином:
ref
, або може не сподобатися той факт, що так звані "незмінні структури" не є, особливо в боксі. Дуже погано .net ніколи не реалізовував концепцію передачі параметрів примусовим виконанням const ref
, оскільки в багатьох випадках така семантика є тим, що насправді потрібно.
Dictionary
, наприклад тут: stackoverflow.com/questions/5850243 /…
Для двох кортежів ви все ще можете використовувати KeyValuePair <TKey, TValue> з попередніх версій загальної системи типів. Це тип значення.
Незначним уточненням статті Метта Елліса було б те, що різниця у семантиці використання між посиланням та типами значень є лише "незначною", коли діє незмінність (що, звичайно, має місце тут). Тим не менше, я думаю, було б найкращим у дизайні BCL не вносити плутанину в тому, що Tuple переходить на еталонний тип на якомусь порозі.
Не знаю, але якщо ви коли-небудь використовували 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 #