Атрибут Структуры Дискриминационных Союзов - PullRequest
3 голосов
/ 14 января 2020

Я только что понял, что записи F # являются ссылочными типами и сколько я занимаюсь боксом и распаковкой. У меня есть много крошечных записей, подобных этому:

type InputParam =
    | RegionString of string
    | RegionFloat of float32

Но если я попытаюсь пометить его атрибутом "Struct", я получу сообщение об ошибке компилятора "FS3204 Если тип объединения имеет более одного случая и является структурой, тогда все поля в типе объединения должны иметь уникальные имена. " Справочник по языку показывает, как создавать структурно-разграниченные объединения, например:

[<Struct>]
type InputParamStruct =
    | RegionString of RegionString: string
    | RegionFloat of RegionFloat: float32

В чем разница между x строки и x of x: string? Как поля не являются уникальными для начала? Почему F # по умолчанию не использует структуры для записей?

Ответы [ 2 ]

7 голосов
/ 14 января 2020

Во-первых, это не Отчеты - это Дискриминационные Союзы. Запись - это простая совокупность именованных данных с сгенерированным равенством / хэшированием, и создание из нее структуры также возможно, но не сопряжено с дополнительными требованиями.

Более строгие требования для структуры Дискриминационные объединения:

  • Нет вызываемого конструктора по умолчанию
  • Нет циклов c ссылки / нет рекурсивных определений
  • У нескольких случаев должны быть уникальные имена

Первые два точки присуще тому, чтобы быть типами значения. Значения и ссылочные типы просто разные.

Последний пункт интересен. Рассмотрим следующее:

type DU1 =
    | Case1 of string
    | Case2 of float

[<Struct>]
type DU2 =
    | Case1 of sval: string
    | Case2 of fval: float

В случае DU1 существует внутренний класс для каждого случая, и они содержат свойства для доступа к базовым данным. Эти свойства называются Item1, Item2 и т. Д., И поскольку они инкапсулированы во внутреннем классе, при обращении к ним они уникальны.

В случае DU2, sval и fval значения выложены ровно; нет внутреннего класса, который их содержит. Это потому, что целью является производительность / размер структуры. Стратегия именования данных в объединенном случае (Item1 / Item2 / et c.) Не применяется, потому что все данные выложены ровно. И поэтому дизайнерское решение состояло в том, чтобы потребовать уникальных именованных случаев, а не применять некоторые хитрости для объединения имени самого случая и некоторого изменения Item1 / Item2 / et c. Проблема уникальности присуща дизайну самих союзов в компиляторе, а не просто выбору дизайна codegen.

Наконец, на этот вопрос есть еще один интересный ответ:

Почему нет F # по умолчанию для структур для записей?

Кортежи, записи и DU в F # могут быть помечены как [<Struct>], но не являются структурами по умолчанию. Это потому, что структуры - это не просто кнопка «сделать ее более эффективной», которую вы можете использовать. Часто производительность процессора снижается из-за чрезмерного копирования, поскольку ваши структуры слишком велики. В F # вполне нормально иметь большие кортежи и очень очень большие записи и различающиеся объединения. Создание этих структур по умолчанию не будет хорошим выбором. Ссылочные типы очень мощные и рассчитаны на очень хорошую работу. NET, и их не следует избегать по умолчанию только потому, что в некоторых случаях структура может привести к несколько большей производительности.

Когда вы беспокоитесь о производительности, никогда не меняйте вещи только на основании предположений или интуиции: используйте инструменты профилирования, такие как PerfView, dotTrace или dotMemory; и оцените небольшие изменения с помощью статистических инструментов, таких как BenchmarkDo tNet. Производительность является чрезвычайно сложным пространством и редко становится чем-то простым, когда вы закончите с учетом вопиющих проблем, которые явно плохие (например, алгоритмы O (n ^ 2) для больших наборов данных или что-то в этом роде).

1 голос
/ 15 января 2020

Без вопросов, это должна быть структура. Это неизменный и 16 байтов. Глядя на разборку, этот тип ссылки:

type InputParam =
    | RegionString of string
    | RegionFloat of float32

И этот тип ссылки:

type InputParam =
    | RegionString of RegionString: string
    | RegionFloat of RegionFloat: float32

Функционально идентичны. Разница лишь в том, как компилятор назвал вещи. Они оба создают подкласс с именем «RegionString», но с разными именами свойств - «RegionString.item» и «RegionString.RegionString».

Когда вы преобразуете первый пример в структуру, он избавляется от подклассов и пытается прикрепить 2 свойства «item» к записи, что приводит к ошибке уникального имени FS3204 .

Что касается производительности, при компоновке вы должны использовать структуры для каждого крошечного типа, подобного этому. Рассмотрим пример этого сценария:

type Name = Name of string
let ReverseName (Name s) =
    s.ToCharArray() |> Array.rev |> System.String |> Name

[<Struct>]
type StrName = StrName of string
let StrReverseName (StrName s) =
    s.ToCharArray() |> Array.rev |> System.String |> StrName

#time
Array.init 10000000 (fun x -> Name (x.ToString()))
|> Array.map ReverseName
|> ignore
#time

#time
Array.init 10000000 (fun x -> StrName (x.ToString()))
|> Array.map StrReverseName
|> ignore
#time

sizeof<Name>
sizeof<StrName>

Первый оборачивает тип ссылки в тип ссылки, который удваивает удар по производительности:

Real: 00:00:04.637, CPU: 00:00:04.703, GC gen0: 340, gen1: 104, gen2: 7
...
Real: 00:00:02.620, CPU: 00:00:02.625, GC gen0: 257, gen1: 73, gen2: 1
...
val it : int = 8
val it : int = 8

Функциональное моделирование доменов - это круто, но вы должны имейте в виду, что они имеют одинаковую нагрузку на производительность:

let c = CustomerID 5
let i = 5 :> obj

Рекомендуется , все неизменяемые до 16 байтов должны иметь структуру . Если это было более 16 байтов, вы должны были посмотреть на поведение. Если его много раздают, возможно, лучше передать 64-битный указатель ref и принять удар по заголовку ref. Но для внутренних данных при составлении типов или внутри функции придерживайтесь структур.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...