Когда моя структура слишком велика?

Нам рекомендуется использовать struct вместо class в Swift.

Это потому что

  1. Компилятор может сделать много оптимизаций
  2. Экземпляры создаются в стеке, что намного более эффективно, чем вызовы malloc/free.

Недостатком переменных struct является то, что они копируются каждый раз, когда возвращаются из функции или присваиваются ей. Очевидно, что это тоже может стать узким местом.

Например. представьте себе матрицу 4x4. 16 Значения с плавающей запятой должны копироваться при каждом назначении/возврате, что составляет 1024 бита в 64-битной системе.

Один из способов избежать этого — использовать inout при передаче переменных в функции, что в основном является способом создания указателя в Swift. Но тогда нам также не рекомендуется использовать inout.

Итак, на мой вопрос:
Как мне обрабатывать большие неизменяемые структуры данных в Swift?
Нужно ли мне беспокоиться о создании большого struct со многими членами?
Если да, то когда я перейду черту?


person IluTov    schedule 31.05.2015    source источник
comment
Мы воодушевлены Кто придумал эту политику? Кто-то, кто хочет предотвратить хороший код?   -  person idmean    schedule 31.05.2015
comment
@idmean Имеет смысл использовать структуры. Помните, этот совет в основном исходит от команды Swift, наверняка они знают, сами написали оптимизатор.   -  person IluTov    schedule 31.05.2015
comment
Вероятно, в некоторых случаях эти доли секунд имеют значение, но я обычно считаю, что хорошо структурированный код помогает предотвратить ошибки, что также приводит к лучшему и быстрому коду. Но только мое мнение.   -  person idmean    schedule 31.05.2015
comment
Структуры способствуют неизменности, что во многих случаях помогает предотвратить ошибки. Разница не в доли секунды, но может быть весьма существенной.   -  person IluTov    schedule 31.05.2015
comment
Структуры также устраняют необходимость в динамической диспетчеризации, которая является одним из наиболее важных улучшений скорости (если только вам не нужен полиморфизм).   -  person IluTov    schedule 31.05.2015
comment
Хорошо, мы могли бы обсуждать это целую вечность. На ваш вопрос: конечно, очень большие структуры плохи, поскольку они, как вы сказали, выделены в стеке.   -  person idmean    schedule 31.05.2015
comment
Чистые классы Swift не используют динамическую отправку и модификаторы доступа (в соответствии с неизменностью).   -  person idmean    schedule 31.05.2015
comment
@idmean Да, это правда, я дам тебе это. Конечно, очень большие структуры — это плохо. Тогда насколько большим является очень большой?   -  person IluTov    schedule 31.05.2015
comment
Он слишком велик в тот момент, когда он становится узким местом. Если у вас есть огромная структура, которая будет копироваться только раз в минуту в фоновом потоке, и никто этого не заметит, то она не слишком велика.   -  person NiñoScript    schedule 31.05.2015
comment
@NiñoScript Ну, конечно, но когда это становится узким местом, то есть когда выделение в куче становится быстрее, чем создание его в стеке и, например, передать его функции.   -  person IluTov    schedule 31.05.2015
comment
@NSAddict Я думаю, это то, что вам нужно проверить. Вы узнаете, что это узкое место, когда ваше приложение должно быть быстрее, вы открываете свой профилировщик (Инструменты), и он ясно говорит, что узкое место — это копия. Не отказывайтесь от безопасности вашей большой структуры до тех пор, пока она не пострадает, иначе вы станете еще одной жертвой преждевременной оптимизации.   -  person NiñoScript    schedule 31.05.2015


Ответы (2)


Этот принятый ответ не полностью отвечает на ваш вопрос: Swift всегда копирует структуры. Трюк, который делают Array/Dictionary/String/etc, заключается в том, что они являются просто обертками вокруг классов (которые содержат фактические сохраненные свойства). Таким образом, sizeof(Array) - это просто размер указателя на этот класс (MemoryLayout<Array<String>>.stride == MemoryLayout<UnsafeRawPointer>.stride)

Если у вас действительно большая структура, вы можете захотеть обернуть ее сохраненные свойства в класс для эффективной передачи в качестве аргументов и проверить isUniquelyReferenced перед мутацией, чтобы получить семантику COW.

У структур есть и другие преимущества эффективности: они не требуют подсчета ссылок и могут быть разложены оптимизатором.

В Swift значения хранят уникальную копию своих данных. Есть несколько преимуществ использования типов значений, например, обеспечение того, чтобы значения имели независимое состояние. Когда мы копируем значения (эффект присваивания, инициализации и передачи аргументов), программа создает новую копию значения. Для некоторых больших значений эти копии могут занимать много времени и снижать производительность программы.

https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst#the-cost-of-large-swift-values

Также раздел о типах контейнеров:

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

person karwag    schedule 02.06.2017
comment
Ты прав. Но разве накладные расходы, связанные с подсчетом ссылок и выделением кучи, не уничтожат преимущества использования структур в производительности? Тогда единственным преимуществом будет семантика COW. - person IluTov; 06.06.2017
comment
Ну, это зависит от того, как вы думаете об этом. Опыт Apple с Foundation научил их тому, что String, Array и т. д. должны быть значениями. Тот факт, что они не являются типами значений в Objective-C, означает, что вам часто нужно копировать их для защиты, чтобы гарантировать, что у вас есть уникальная ссылка на базовое хранилище. COW — это большой выигрыш в производительности, потому что он заменяет эти защитные копии теми, которые, как известно, требуются (например, потому что две ссылки на массив указывают на одну и ту же резервную копию, а одна хочет мутировать). - person karwag; 24.07.2017
comment
Кроме того, для элементов с изменяемым размером всегда требуется выделение кучи. В противном случае размер любого кадра стека, использующего массив или строку, будет зависеть от его длины. Я помню, что в первой бета-версии Swift массивы с let-аннотациями все еще были изменяемыми, если вы не меняли их длину. Может быть, они пытались пойти на что-то подобное. Людям не нравилась эта система, они протестовали, и вместо этого мы получили COW. - person karwag; 24.07.2017
comment
Это имеет смысл. Хотя нам редко приходилось защитно копировать массивы и тому подобное, потому что по умолчанию они были неизменяемыми. Думаю, самым большим плюсом является то, что у нас есть один тип как для изменяемых, так и для неизменяемых массивов. - person IluTov; 24.07.2017
comment
Также стоит отметить, что COW появился в Obj-C в iOS 11, поэтому эти защитные копии не должны быть такими дорогими. Swift по-прежнему, вероятно, будет быстрее из-за дженериков времени компиляции, статической диспетчеризации и агрессивного встраивания. Сравнение структуры и класса не должно иметь большого значения: структуры не имеют подтипов или виртуальных членов, но компилятор Swift в любом случае может девиртуализовать неоткрытые классы внутри одного и того же модуля. Просто более легкая семантика. - person karwag; 25.07.2017
comment
Согласовано. Конечные классы должны иметь очень похожую производительность, за исключением накладных расходов, связанных с выделением кучи. - person IluTov; 26.08.2017

В самом низу этой страницы из Быстрая ссылка:

ПРИМЕЧАНИЕ

Приведенное выше описание относится к «копированию» строк, массивов и словарей. Поведение, которое вы видите в своем коде, всегда будет таким, как если бы имело место копирование. Однако Swift выполняет реальную копию за кулисами только тогда, когда это абсолютно необходимо. Swift управляет копированием всех значений, чтобы обеспечить оптимальную производительность, и вам не следует избегать присваивания, чтобы попытаться упредить эту оптимизацию.

Я надеюсь, что это ответит на ваш вопрос, также, если вы хотите быть уверенным, что массив не будет скопирован, вы всегда можете объявить параметр как inout и передать его с &array в функцию.

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

Примеры структур:

  • Часовой пояс
  • Широта Долгота
  • Размер/Вес

Примеры для классов:

  • Человек
  • Вид
person Kametrixom    schedule 31.05.2015
comment
Это потрясающе! Я действительно читал это, когда вышел Swift, и я совершенно забыл об этом. - person IluTov; 03.06.2015