Почему новый тип Tuple в .Net 4.0 - ссылочный тип (класс), а не тип значения (struct)

Кто-нибудь знает ответ и/или имеет оппортунизм?

Поскольку кортежи обычно не были бы очень большими, я бы предположил, что было бы разумнее использовать для них структуры, чем классы. Что вы говорите?

Ответы

Ответ 1

Microsoft сделала все типы типов типов кортежей в интересах простоты.

Я лично считаю, что это была ошибка. Кортежи с более чем 4 полями очень необычны и в любом случае должны быть заменены более типовой альтернативой (например, тип записи в F #), поэтому практический интерес представляют только небольшие кортежи. Мои собственные тесты показали, что распакованные кортежи длиной до 512 байтов могут быть быстрее, чем коробочные кортежи.

Хотя эффективность памяти является одной из проблем, я считаю, что доминирующей проблемой является накладные расходы сборщика мусора .NET. Распределение и сборка очень дороги на .NET, потому что сборщик мусора не очень сильно оптимизирован (например, по сравнению с JVM). Более того, стандартная платформа .NET GC (рабочая станция) еще не была распараллелена. Следовательно, параллельные программы, которые используют кортежи, останавливаются, так как все ядра конкурируют за общий сборщик мусора, уничтожая масштабируемость. Это не только основная проблема, но, AFAIK, полностью игнорировалось Microsoft, когда они рассматривали эту проблему.

Еще одна проблема - виртуальная отправка. Типы ссылок поддерживают подтипы, и поэтому их члены обычно вызываются посредством виртуальной отправки. Напротив, типы значений не могут поддерживать подтипы, поэтому вызов участника абсолютно однозначен и всегда может выполняться как прямой вызов функции. Виртуальная диспетчеризация чрезвычайно дорогостоящая на современном оборудовании, потому что CPU не может предсказать, где закончится счетчик программ. JVM подходит для оптимизации виртуальной отправки, но .NET нет. Однако .NET обеспечивает переход от виртуальной отправки в виде типов значений. Таким образом, представление кортежей в качестве типов значений также может привести к значительному повышению производительности. Например, вызов GetHashCode в двухстрочном массиве миллион раз занимает 0,17 с, но вызов его на эквивалентной структуре занимает всего 0,008, т.е. Тип значения в 20 раз быстрее, чем ссылочный тип.

Реальная ситуация, когда эти проблемы производительности с кортежами обычно возникают, заключается в использовании кортежей в качестве ключей в словарях. Я фактически наткнулся на этот поток, перейдя по ссылке из вопроса о переполнении стека F # запускает мой алгоритм медленнее, чем Python!, где авторская программа F # оказалась медленнее, чем его Python именно потому, что он использовал коробочные кортежи. Ручная распаковка с использованием рукописного типа struct делает его программу F # в несколько раз быстрее и быстрее, чем Python. Эти проблемы никогда бы не возникли, если кортежи были представлены типами значений, а не ссылочными типами, чтобы начать с...

Ответ 2

Причина, скорее всего, потому, что только меньшие кортежи имели бы смысл как типы значений, так как они имели бы небольшой объем памяти. Большие кортежи (т.е. Те, у которых больше свойств) действительно пострадают от производительности, поскольку они будут больше 16 байт.

Вместо того, чтобы некоторые кортежи были типами значений, а другие - ссылочными типами и заставляли разработчиков знать, какие из них я представляю, люди в Microsoft думают, что их все ссылочные типы были проще.

А, подозрения подтвердились! См. Building Tuple:

Первым важным решением было обрабатывать кортежи либо как ссылку или тип значения. Поскольку они неизменяемый в любое время, когда вы хотите изменить значения кортежа, вы должны создать новый. Если они ссылочные типы, это означает, что будь то мусор, если вы меняют элементы в кортеже в плотная петля. Корзины F # были ссылкой типы, но было ощущение от чтобы они могли реализовать улучшение производительности, если два, и возможно, три, элементарные кортежи были значения типов. Некоторые команды, которые создал внутренние кортежи вместо ссылочных типов, потому что их сценарии были очень чувствительный к созданию большого количества управляемых объекты. Они обнаружили, что использование значения тип дал им лучшую производительность. В наш первый проект кортежа спецификации, мы сохранили двух-, трех- и четырехэлементных кортежей как типы ценностей, а остальные ссылочные типы. Однако во время дизайн-совещание, которое включало представители других языков было решено, что этот "раскол" дизайн будет путать, из-за немного отличающаяся семантика между эти два типа. Согласованность в поведении и конструкция была определена как более высокий приоритет, чем потенциал производительность увеличивается. Основываясь на этом ввода, мы изменили дизайн так, чтобы все кортежи являются ссылочными типами, хотя мы попросили команду F # сделать некоторые исследования эффективности, чтобы увидеть если он испытывал ускорение при использовании тип значения для некоторых размеров кортежей. У него был хороший способ проверить это, поскольку его компилятор, написанный на F #, был хороший пример большой программы, которая используемые кортежи в различных сценариях. В итоге команда F # обнаружила, что она не улучшилось когда некоторые кортежи были типами значений вместо ссылочных типов. Это сделано нам стало лучше о нашем решении используйте ссылочные типы для кортежа.

Ответ 3

Если типы .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 #, и они очень часто используются на этом языке, кортежи "struct" будут представлять программистов F # под угрозой написания неэффективных программ, даже не зная об этом. Это произойдет так легко:

let t3 = 1L, 2L, 3L
let t33 = t3, t3, t3

По-моему, "структурные" кортежи будут вызывать высокую вероятность создания значительных неэффективности в повседневном программировании. С другой стороны, существующие "классовые" кортежи также вызывают определенные неэффективности, о чем упоминает @Jon. Тем не менее, я думаю, что продукт "вероятности возникновения" раз "потенциальный урон" был бы намного выше с помощью структур, чем в настоящее время с классами. Поэтому текущая реализация - это меньшее зло.

В идеале были бы и кортежи "class", и "struct" кортежи, как с синтаксической поддержкой в ​​F #!

Изменить (2017-10-07)

Структурные кортежи теперь полностью поддерживаются следующим образом:

  • Построено в mscorlib (.NET >= 4.7) как System.ValueTuple
  • Доступно как NuGet для других версий
  • Синтаксическая поддержка в С# >= 7
  • Синтаксическая поддержка в F # >= 4.1

Ответ 4

Для 2-х кортежей вы все равно можете использовать KeyValuePair < TKey, TValue > из более ранних версий системы Common Type. Это тип значения.

Небольшое разъяснение к статье Мэтта Эллиса будет заключаться в том, что разница в семантике использования между ссылочными и значениями типов является лишь "незначительной", когда действует неизменяемость (что, конечно, будет иметь место здесь). Тем не менее, я думаю, что было бы лучше всего в дизайне BCL не вводить путаницу в том, что Tuple перейдет к эталонному типу при некотором пороге.

Ответ 5

Я не знаю, но если вы когда-либо использовали F # Tuples, это часть языка. Если бы я сделал .dll и вернул тип Tuples, было бы неплохо иметь тип, чтобы вставить это. Я подозреваю, что теперь 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)