в чем разница между классом и структурой c
Классы или структуры, в чем отличия
Чтобы лучше понять, когда использовать класс или структуру, рассмотрим различия между структурами и классами. Дополнительный пример см. здесь.
Структуры синтаксически очень похожи на классы, но существует принципиальное отличие, которое заключается в том, что класс – является ссылочным типом (reference type), а структуры – значимым типом (value type) (см. статью «Типы данных«). Следовательно, классы всегда создаются в так называемой “куче” (heap), а структуры создаются в стеке (stack).
Но это справедливо в очень простых случаях, главное отличие структур и классов: структуры, указываемые в списке параметров метода, передаются по значению (то есть копируются), объекты классов — по ссылке. Именно это является главным различием в их поведении, а не то, где они хранятся. Примечание: структуру тоже можно передать по ссылке, используя модификаторы out и ref.
Чем больше вы будете использовать структуры вместо маленьких классов, тем менее затратным по ресурсам будет использование памяти.
Так же как и классы, структуры могут иметь поля, методы и конструкторы. О конструкторах структур уже говорилось, ибо это важный критерий при сравнивании классов и структур.
Мы выяснили, что все встроенные типы значений задаются структурами, например, числовые типы int, long, float определены структурами System.Int32, System.Int64 и System.Single соответственно. Эти структуры имеют поля и методы. Мы уже вызывали методы у переменных этих типов. Например, каждая из перечисленных структур имеет метод ToString( ). Также у перечисленных структур есть статичные поля, например, Int32.MaxValue или Int32.MinValue. Получается, что мы уже многократно использовали структуры.
В отличие от классов, использование публичных полей в структурах в большинстве случаев не рекомендуется, потому что не существует способа контролирования значений в них. Например, кто-либо может установить значение минут или секунд более 60. Более правильный вариант в данном случае использовать свойства, а в конструкторе осуществить проверку:
В результате будет напечатано число часов: 6 (остаток от деления 30 на 24).
Заменим конструктор Time(…) конструктором без параметров:
Получим сообщение об ошибке:
«Структуры не могут содержать явных конструкторов без параметров»
Причина возникновения ошибки в том, что вы не можете использовать конструктор по умолчанию (без параметров) для структуры, потому что компилятор всегда генерирует его сам.
Что же касается класса, то компилятор создает конструктор по умолчанию только в том случае, если Вы его не создали. При объявлении класса нет проблем создать собственный конструктор без параметров (замените в программе ключевое слово struct на class, вы получите результат — 7).
Еще один эксперимент. Замените снова class на struct и удалите полностью конструктор Time(). Запустите программу, она будет выполнена, результат – 0. Вы получите три предупреждения типа:
«Полю «ConsoleApplication1.Time.hours» нигде не присваивается значение, поэтому оно всегда будет иметь значение по умолчанию 0».
Это означает, что был сгенерирован конструктор (без параметров) для структуры, который всегда устанавливает поля в 0, false или null (для объектов) – как и для классов. Поэтому Вы можете быть уверенными в том, что созданная структура всегда будет вести себя адекватно в соответствии со значениями по умолчанию в используемых типах.
Если Вы не хотите использовать значения по умолчанию, то можете инициализировать поля своими значениями в конструкторе с параметрами для инициализации.
Однако если в этом конструкторе не будет инициализировано какое-нибудь значение, компилятор не будет его инициализировать и покажет ошибку.
Два правила структур
Первое правило структуры: Всегда все переменные должны быть инициализированы.
В классах Вы можете инициализировать значение полей непосредственно при их объявлении. В структурах такого сделать нельзя, и поэтому данный код вызовет ошибку при компиляции. Поэтому:
Второе правило структуры: Нельзя инициализировать переменные в том месте, где они объявляются.
Сравнение классов и структур в сжатом виде:
Вопрос | Структура | Класс |
Какого же типа экземпляр объекта? | Значимый (value) тип | Ссылочный (reference) тип |
Где “живут” экземпляры этих объектов? | Экземпляры структуры называют значениями и “живут” они в стеке (stack). | Экземпляры классов называют объектами и “живут” они в куче (heap). |
Можно ли создать конструктор по умолчанию? | Нет | Да |
Если создается свой конструктор, будет ли компилятор генерировать конструктор по умолчанию? | Да | Нет |
Если в своём конструкторе не будут инициализированы некоторые поля, будут ли они автоматически инициализированы компилятором? | Нет | Да |
Разрешается ли инициализировать переменные там, где их объявляют? | Нет | ДА |
Поля структуры могут быть инициализированы при использовании конструктора (если объект объявляется с помощью оператора new), причем не важно, какого «собственного» или «по умолчанию».
К особенностям структур можно отнести еще и тот факт, что вследствие того, что структуры являются значимым типом, то можно создать структуру без использования конструктора, например:
В таком случае, переменная t создается, но поля не будут инициализированы конструктором (нет оператора Time t = new Time();). Правда, теперь поля структуры придется объявлять только с модификатором public.
Замечания, над которыми стоит подумать (проверить практикой):
1. Используйте структуры, это признак хорошего тона в программировании на C#, хотя некоторые авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы.
2. Дополнения: структуры не могут быть абстрактными, структуры не имеют деструкторов, структуры не поддерживают наследование.
3. Главное отличие структур и классов: структуры передаются по значению (то есть копируются), объекты классов — по ссылке. Именно это является существенным различием в их поведении, а не то, где они хранятся.
4. Структуру тоже можно передать по ссылке, используя модификаторы out и ref.
Ответ: 400016 байт. Конец примечания.
Теперь после обсуждения структур перейдем к рассмотрению массивов — важнейших конструкций данных, всегда размещаемых в управляемой куче и являющихся объектами.
Выбор между классом и структурой
Одним из основных решений по проектированию каждого разработчика инфраструктуры является необходимость разрабатывать тип как класс (ссылочный тип) или как структура (тип значения). Чтобы сделать это, важно понимать различия в поведении ссылочных типов и типов значений.
Первое различие между ссылочными типами и типами значений будет рассмотрено в том, что ссылочные типы выделяются в куче и уничтожаются сборщиком мусора, тогда как типы значений выделяются либо в стеке, либо в виде встроенных строк, содержащих типы, и освобождаются при освобождении стека или при освобождении содержащего их типа. Таким образом, выделение и освобождение типов значений являются общими дешевле, чем выделение и освобождение ссылочных типов.
Затем массивы ссылочных типов выделяются вне строки, что означает, что элементы массива являются просто ссылками на экземпляры ссылочного типа, находящегося в куче. Массивы типов значений выделяются встроенным, то есть элементы массива являются реальными экземплярами типа значения. Таким образом, выделение и освобождение массивов типов значений значительно дешевле, чем выделение и освобождение массивов ссылочного типа. Кроме того, в большинстве случаев массивы типов значений представляют гораздо более высокий уровень локализации ссылок.
Следующее отличие связано с использованием памяти. Типы значений упаковываются при приведении к ссылочному типу или к одному из интерфейсов, которые они реализуют. Они будут распакованы при приведении обратно к типу значения. Так как поля представляют собой объекты, выделенные в куче и собираемые сборщиком мусора, слишком много упаковки и распаковки могут негативно повлиять на кучу, сборщик мусора и в конечном итоге производительность приложения. В отличие от этого, такая упаковка не происходит, так как ссылочные типы являются приведенными. (Дополнительные сведения см. в разделе Упаковка и распаковка).
Затем назначения ссылочного типа копируют ссылку, тогда как назначения типа значения копируют все значение. Таким образом, присваивания больших ссылочных типов дешевле, чем присваивания типов больших значений.
Наконец, ссылочные типы передаются по ссылке, тогда как типы значений передаются по значению. Изменения в экземпляр ссылочного типа влияют на все ссылки, указывающие на экземпляр. Экземпляры типа значения копируются, когда они передаются по значению. При изменении экземпляра типа значения это само по себе не влияет на его копии. Поскольку копии не создаются явным образом пользователем, но неявно создаются при передаче аргументов или возвращении возвращаемых значений, типы значений, которые могут быть изменены, могут вызвать путаницу для многих пользователей. Поэтому типы значений должны быть неизменяемыми.
Как правило, большинство типов в платформе должны быть классами. Однако существуют ситуации, в которых характеристики типа значения делают более подходящим использование структур.
✔️ Рассмотрите возможность определения структуры вместо класса, если экземпляры типа небольшие и часто используются кратковременно или обычно внедряются в другие объекты.
❌ НЕ следует определять структуру, если тип имеет не все приведенные ниже характеристики.
Он имеет размер экземпляра менее 16 байт.
Он не должен быть часто в штучной упаковке.
Во всех остальных случаях типы следует определять как классы.
Части © 2005, 2009 Корпорация Майкрософт. Все права защищены.
C# NET: Class vs Struct или в чём различия между Классом и Структурой
Просто о NET | создано: 10.07.2011 | опубликовано: 10.07.2011 | обновлено: 06.12.2021 | просмотров: 91208 | комментариев: 30
Мне в последнее время очень часто встречаются программисты, которые не только используют в обычной “программной” жизни структуры (struct), но вообще, ничего не знают об этом объекте. И зачастую, для простоты своей «программной» жизни используют всегда классы (class). В этой статье я бы хотел в очередной раз остановиться на различиях между структурами и классами.
Что такое struсture?
Ключевой момент статьи: Чем больше Вы будете использовать структуры вместо небольших (наверное, правильнее будет сказать – маленьких) классов, тем менее затратным по ресурсам будет использование памяти. Смею предположить, что не требуется объяснения почему… “куча”, “сборщик мусора”, “неопределенные временные интервалы прохода”, сложность “ручного” управления кучей и сборщиком мусора. Все ключевые моменты уже перечислены.
Так же как и классы, структуры могут иметь поля, методы и конструкторы. Хотя про конструкторы надо поговорить подробнее (будет дальше по теме), ибо это есть очень важное понятие при сравнивании классов и структур.
Структуры всегда с ВамиНе хочется думать, что следующая информация, для Вас сюрпризом. В языке C# примитивные числовые типы int, long, float являются альясами для структур System.Int32, System.Int64 и System.Single соответственно. Эти структуры имеют поля и методы. Вы обычно вызываете методы у переменных данных типов. Например, каждая из перечисленных структур имеет метод ToString. Также у перечисленных структур есть статичные поля, например, Int32.MaxValue или Int32.MinValue. Получается, что Вы уже в повседневной «программной» жизни используете структуры, а значит знакомы с ними.
Таблица классов и структур в Microsoft. NET Framework
В таблице указаны альясы и соответствующие им типы, а также дана информация о представляющем типе (структура или класс).
Keyword | Type equivalent | Class or structure |
---|---|---|
bool | System.Boolean | Structure |
byte | System.Byte | Structure |
decimal | System.Decimal | Structure |
double | System.Double | Structure |
float | System.Single | Structure |
int | System.Int32 | Structure |
long | System.Int64 | Structure |
object | System.Object | Class |
sbyte | System.SByte | Structure |
short | System.Int16 | Structure |
string | System.String | Class |
uint | System.UInt32 | Structure |
ulong | System.UInt64 | Structure |
ushort | System.UInt16 | Structure |
Объявление структур
Для объявления структуры используется зарезервированное слово struct, следом наименование структуры и фигурные скобки:
Кстати, …По умолчанию, Вы не можете использовать некоторые общие операторы в Ваших структурах. Например, Вы не можете использовать оператор сравнения (==) и противоположные ему (!=) на своих структурах. Однако, Вы можете явно объявить и реализовать операторы для своих структур.
И в чем же разница между структурами и классами
Давайте рассмотрим пример, в котором уже заложена ошибка:
Причина возникновении ошибки в том, что Вы не можете использовать конструктор по умолчанию (без параметров) для структуры, потому что компилятор всегда генерирует его сам. Что же касается класса, то компилятор создает конструктор по умолчанию, только в том случае, если Вы его не создали. Сгенерированный конструктор для структуры всегда устанавливает поля в 0, false или null – как и для классов. Поэтому Вы можете быть уверенными в том, что созданная структура всегда будет вести себя “адекватно” в соответствии со значениями по умолчанию в используемых типах. Если Вы не хотите использовать значения по умолчанию, Вы можете инициализировать поля своими значениями в конструкторе с параметрами для инициализации. Однако, если в этом конструкторе не будет инициализировано какое-нибудь значение, компилятор не будет его инициализировать за Вас и покажет ошибку.
Первое правило Структуры: Всегда все переменные должны быть инициализированы!
В классах Вы можете инициализировать значение полей непосредственно в месте их объявления. В структурах такого сделать не получится, и поэтому данный код вызовет ошибку при компиляции:
Второе правило Структуры: Нельзя инициализировать переменные в месте их объявления!
Данная таблица в некотором роде подытоживает всё вышесказанное и отображает основные отличия между классами и структурами.
Вопрос | Структура | Класс |
И какого же типа экземпляр объекта? | Структура значимый (value) тип | Класс ссылочный (reference) тип |
А где “живут” экземпляры этих объектов? | Экземпляры структуры называют значениями и “живут” они в стеке (stack). | Экземпляры классов называют объектами и “живут” они в куче (heap). |
Можно ли создать конструктор по умолчанию? | Нет | Да |
Если создается свой конструктор будет ли компилятор генерировать конструктор по умолчанию? | Да | Нет |
Если в своём конструкторе не будут инициализированы некоторые поля, будут ли они автоматически инициализированы компилятором? | Нет | Да |
Разрешается ли инициализировать переменные в месте их объявления? | Нет | Да |
Использование структур как переменных
После того как Вы создали структуры, Вы можете использовать ее также как классы и другие типы. Например, создав структуру Time, я могу использовать ее в классе:
Вы можете создавать nullable версию переменной типа структуры использую модификатор “?” и потом присвоить ей значение null:
Time? currentTime = null;
Инициализация структур
В статье я не раз говорил, что поля структуры могут быть инициализированы при использования конструктора, причем не важно какого “собственного” или “по умолчанию”. К особенностям структур можно отнести еще и тот факт, что вследствие того, что структуры являются значимым типом, то можно создать структуру без использования конструктора, например:
В таком случае, переменная создается, но поля не будут инициализированы в соответствии параметрами конструктора.
Заключение
Используйте структуры, это признак хорошего тона в программировании на C# и да прибудет с Вами сила… структур. Цитата из комментария: «С заключением тоже не согласен. Многие маститые западные авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы. Хотя, конечно, молиться на них и их мнение не стоит, лучше думать своей головой.»
C#: Отличие класса от структуры (class vs struct)
Сегодня я решил рассказать о одном из частых вопросах на собеседованиях «чем отличается класс от структуры в C#«.
На самом деле ответить на этот вопрос можно очень коротко. Структура в C# является типом передающимся по значению, класс же является ссылочным типом. По большей части сказать больше нечего. Однако данный ответ врятли внесет какую то ясность людям, которые не понимают что значит передавать по ссылке или передавать по значению. Поэтому давайте рассмотрим данный вопрос подробней на примерах. Предположим что у нас есть «class cA» и struct «sA».
Первое важное отличие ссылочного типа от типа передаваемого по значению это то, что значимый тип не может быть равен null.
Так как структура не может быть равна null то она должна быть инициализирована. При этом инициализация структуры происходит по умолчанию нулевыми значениями. В следствие этого вы не можете создать конструктор по умолчанию для структур. А как следствие не можете инициализировать значения переменных в структурах inline.
Но главным различаем данных двух конструкций является их поведение при присваивании. Это повередение очень легко рассмотреть на примере:
Данный пример очень наглядно демонстрирует различие между передачей значения по ссылки и по значению. Как видно передача по значению подрузомевает создание новой переменной и копирование в нее всех значений. Что в дальнейшем позволяет работать с двумя различными объектами. Передача же по ссылке подрузомевает создание новой ссылки на уже имеющиеся значения.
Если проводить аналогию, например, с Windows, то передача по значению — это копирование ваших файлов, а передача по ссылке — создание ярлыков.
Структуры против Классов
С самого начала, когда я начинал заниматься программированием, вставал вопрос, что использовать для улучшения быстродействия: структуру или класс; какие массивы лучше использовать и как. По поводу структур, Apple приветствует их использование, объясняя это тем, что они лучше поддаются оптимизации, и вся суть языка Swift — это структуры. Но есть и те, кто с этим не согласен, ведь можно красиво упростить код, отнаследовав один класс от другого и работая с таким классом. Для ускорения работы с классами создавали разные модификаторы и объекты, которые оптимизировались специально под классы, и тут уже сложно сказать, что и в каком случае будет быстрее.
Для расстановки всех точек на “е” я написал несколько тестов, которые используют обычные подходы к обработке данных: передача в метод, копирование, работа с массивами и так далее. Громких выводов я решил не делать, каждый решит для себя сам, стоит ли верить тестам, сможет скачать проект и посмотреть, как он будет работать у вас, и попробовать оптимизировать работу того или иного теста. Возможно даже выйдут новые фишки, которые я не упомянул, или они настолько редко используются, что я просто не слышал о них.
P.S. Я начинал работу над статьёй на Xcode 10.3 и было думал попробовать сравнить его скорость с Xcode 11, но всё же статья не про сравнение двух приложений, а о скорости работы наших приложений. Я не сомневаюсь, что время работы функций уменьшится, и то, что было плохо оптимизировано, станет быстрее. В итоге я дождался новый Swift 5.1 и решил проверить гипотезы на практике. Приятного чтения.
Тест 1: Сравним массивы на структурах и классах
Предположим, у нас есть некий класс, и мы хотим положить объекты этого класса в массив, обычное действие над массивом — это пройтись по нему циклом.
В массиве при использовании в нём классов и попытке по нему пройтись увеличивается количество ссылок, после завершения число ссылок на объект будет уменьшаться.
Если мы пройдемся по структуре, то в момент вызова объекта по индексу создастся копия объекта, смотрящая на ту же область памяти, но помеченная immutable. Сложно сказать, что быстрее: увеличение числа ссылок на объект или создание ссылки на область в памяти с отсутствием возможности её изменить. Проверим это на практике:
Рис. 1: Сравнение получения переменной из массивов на основе структур и классов
Тест 2. Сравненим ContiguousArray vs Array
Что более интересно — сравнить производительность массива (Array) с ссылочным массивом (ContiguousArray), который нужен специально для работы c классами, хранимыми в массиве.
Проверим производительность для следующих случаев:
ContiguousArray, хранящий struct с value типом
ContiguousArray, хранящий struct с String
ContiguousArray, хранящий class с value типом
ContiguousArray, хранящий class с String
Array, хранящий struct с value типом
Array, хранящий struct с String
Array, хранящий class с value типом
Array, хранящий class с String
Так как результаты тестов (тесты: передача в функцию с отключенной оптимизацией inline, передача в функцию c включенной оптимизацией inline, удаление элементов, добавление элементов, последовательный доступ к элементу в цикле) будут включать большое количество проведенных тестов (для 8 массивов по 5 тестов), я приведу наиболее значимые результаты:
Оптимизацией циклов для массивов служит lazy инициализатор коллекций, который позволяет пройтись лишь один раз по всему массиву, даже в том случае, если используется несколько фильтров или мапов над элементами массива.
В использовании структур в качестве инструмента оптимизации присутствуют свои подводные камни, такие как использование типов, которые внутри имеют ссылочную природу: строки, словари, ссылочный массивы. Тогда, когда переменная, хранящая в себе ссылочный тип, поступает на вход у какой-либо функции, происходит создание дополнительной ссылки на каждый элемент, который является классом. У этого есть и другая сторона, о ней чуть дальше. Можно попробовать использовать класс-оболочку над переменной. Тогда количество ссылок при передаче в функции увеличится только у неё, а количество ссылок на значения внутри структуры останутся прежними. В целом хочется посмотреть, какое количество переменных ссылочного типа должно быть в структуре, чтобы её производительность снизилась ниже, чем производительность классов с теми же параметрами. В сети есть статья “Stop Using Structs!”, которая задает тот же вопрос и отвечает на него. Я скачал проект и решил разобраться, что где происходит, и в каких случаях мы получаем медленные структуры. Автор показывает низкую производительность структур по сравнению с классами, спорить с тем, что создание нового объекта намного медленное занятие, чем увеличение ссылки на объект, абсурдно (поэтому я убрал строку, где в цикле каждый раз создается новый объект). Но если мы не будем создавать ссылку на объект, а будем просто передавать его в функцию для работы с ним, то разницы в производительности будет очень несущественная. Каждый раз, когда мы ставим inline(never) у функции, наше приложение обязано выполнять её и не создавать кода в строку. Судя по тестам, Apple сделала таким образом, что объект, передаваемый в функцию немного модифицируется, для структур компилятор меняет мутабельность и делает доступ к не мутабельным свойствам объекта ленивыми. В классе происходит нечто подобное, но заодно увеличивает число ссылок на объект. И теперь у нас есть объект lazy, все его поля также lazy, и каждый раз, когда мы вызываем переменную объекта, он его инициализирует. В этом у структур нет равных: при вызове в функции двух переменных, у объекта структура лишь немного уступает классу в скорости; при вызове трёх и более, структура всегда будет быстрее.
Тест 3: Сравниваем производительность Структур и Классов хранящих большие классы
Также я немного изменил сам метод, который вызывался при добавлении еще одной переменной (таким образом в методе инициализировались три переменные, а не две, как в статье), и что бы не было переполнения Int, заменил операции над переменными на сумму и вычитание. Добавил более понятные метрики времени (на скриншоте это секунды, но нам это не так важно, важно понимание получившихся пропорций), удаление фреймворка Darwin (не использую в проектах, возможно зря, разницы в тестах до/после добавления фреймворка в моём тесте нет), включение максимальной оптимизации и билд на релизной сборке (кажется, так будет честнее), и вот результат:
Рис. 2: Производительность структур и классов из статьи “Stop Using Structs”
Разнице в результатах теста незначительны.
Тест 4: Функция, принимающая Generic, Protocol и функция без Generic
Если взять дженерик функцию и передать туда два значения, объединенных только возможностью сравнения этих значений (func min), тогда код из трёх строк превратится в код из восьми (так говорит Apple). Но так бывает не всегда, у Xcode есть способы оптимизации, в которых, если он при вызове функции видит, что в него передается две структурных значения, он автоматически генерирует функцию, принимающую две структуры, и уже не копирует значений.
Рис. 3: Типичная Generic функция
Я решил протестировать две функции: в первой обьявлен тип данных Generic, второй принимает просто Protocol. В новой версии Swift 5.1 Protocol даже немного быстрее, чем Generic (до Swift 5.1 протоколы были в 2 раза медленнее), хотя по версии Apple всё должно быть наоборот, а вот когда дело касается прохода по массиву, нам уже надо приводить тип, что замедляет Generic (но они всё равно молодцы, ведь побыстрее протоколов):
Рис. 4: Сравнение Generic и Protocol принимающей функции.
Тест 5: Сравним вызов родительского метода и родного, а заодно проверим final класс на такой вызов
Что меня так же всегда интересовало, насколько медленно работают классы с большим число родителей, насколько класс быстро вызывает свои функции и функции родителя. В случаях, когда мы пытаемся вызвать метод, который принимает класс, вступает в игру динамическая диспетчеризация. Что это такое? Каждый раз, когда внутри нашей функции вызывается метод или переменная, генерируется сообщение, запрашивая у объекта эту переменную или метод. Объект, получая такой запрос, начинает поиск метода в таблице диспетчеризации своего класса, и если был вызван override метода или переменной, забирает её и возвращает, либо рекурсивно доходит до базового класса.
Рис. 5: Вызовы методов классов, для тестирования диспетчеризация
Из теста выше можно сделать несколько выводов: чем больше у класса родительских классов, тем медленнее он будет работать, и что разница в скорости работы настолько мала, что ей можно смело пренебрегать, скорее всего оптимизации кода сделает так, что разницы в скорости не будет. В данном примере преимущества у final модификатора класса нет, наоборот, работа класса даже медленнее, возможно это связано с тем, что действительно быстрой функцией она так и не становится.
Тест 6: Вызов переменной с модификатором final против обычной переменной класса
Тоже очень интересные результаты с присвоением модификатора final для переменной, его можно использовать, когда точно знаешь, что переменная не будет переписана нигде в наследниках класса. Попробуем поставить модификатор final к переменной. Если бы мы в нашем тесте создали лишь одну переменную и вызывали у неё свойство, тогда бы оно инициализировалось один раз (результат снизу). Если мы по-честному будем создавать каждый раз новый объект и запрашивать его переменную, скорость работы заметно замедлится (результат вверху):
Рис. 6: Вызов final переменной
Очевидно, что на пользу переменной модификатор не пошел, и она всегда медленнее своего конкурента.
Тест 7: Проблема полиморфизма и протоколов для структур. Или производительность Existential container
Проблема: если взять протокол, поддерживающий определенный метод, и несколько структур, наследуемых от этого протокола, то что будет думать наш компилятор, когда мы поместим структуры с разными объёмами хранимых значений в один массив, объединенный изначальным протоколом?
Для решения проблемы вызова метода, предопределенного в наследниках, используется механизм Protocol Witness Table. Он создает структуры оболочки, которые ссылаются на необходимые методы.
Для решения проблемы хранения данных используется Existential container. Он хранит в себе 5 ячеек информации, каждая по 8 байт. В первых трёх выделяется место под хранимые данные в структуре (если они не влезают, то он создает ссылку на кучу, в которой хранятся данные), в четвертой хранится информация о типах данных, которые используются в структуре, и говорит нам о способе управления этими данными, на пятой хранятся ссылки на методы объекта.
Рис 7. Сравнение производительности массива, который создает ссылку на объект и который его содержит
Между первым и вторым результатом число переменных увеличилось втрое. В теории, они должны помещаться в контейнер, они хранятся в этом контейнере, и разница в скорости обусловлена объемом структуры. Что интересно, если вы уменьшите число переменных во второй структуре, то время работы не изменится, то есть контейнер в самом деле хранит 3 или 2 переменные, но по-видимому, для одной переменной существуют особые условия, которые существенно повышают скорость. Вторая структура идеально вписывается в контейнер и отличается по объем от третьей в два раза, что даёт сильную деградацию по времени выполнения, в сравнении с остальными структурами.
Немного теории для оптимизации ваших проектов
На производительность структур могут влиять следующие факторы:
Чем плохи и опасны классы по сравнению со структурами
Мы не всегда управляем копированием наших объектов, а если мы это делаем, мы можем получить слишком много копий, которыми сложно будет управлять (насоздавали объектов в проекте, которые отвечают за формирование вьюшки, например).
Они не такие быстрые как структуры.
Если у нас есть ссылка на объект, и мы пытаемся управлять нашим приложением в многопоточном стиле, мы можем получить Race Condition в случае, когда наш объект используется из двух разных мест (а это не так сложно, ведь собранный с Xcode проект всегда чуть медленнее, чем Store версия).
Если мы пытаемся избежать Race Condition, мы тратим много ресурсов на Lock’и наших данных, что начинает подъедать ресурсы и тратить время вместо быстрой обработки и мы получаем еще более медленные объекты, чем те же самые, построенные на структурах.
Если мы проделываем все упомянутые выше действия над нашими объектами (ссылками), то велика вероятность возникновения непредвиденных deadlock’s.
Сложность кода из-за этого возрастает.
Больше кода = больше багов, всегда!
Выводы
Я подумал, что выводы в этой статье просто необходимы, потому что читать статью из раза в раз не хочется, и сводный перечень пунктов просто необходим. Подводя черту под тестами, хочется выделить следующее: