Всегда ли стек по производительности лучше кучи и какие нюансы работы со стеком в Swift?
Что проверяют
Не заучили ли вы правило «стек быстрее» без понимания, откуда берётся разница и когда она перестаёт работать.
Как отвечать
Обычно стек дешевле: выделение это сдвиг указателя, освобождение происходит само при выходе из функции, память лежит рядом и хорошо ложится в кеш процессора. В куче нужно найти свободный блок, а у объектов классов сверху добавляется подсчёт ссылок ARC с атомарными операциями.
Но «всегда лучше» неверно, и вот почему:
- копирование: значимый тип копируется при каждой передаче. Большая структура на десятки полей копируется дороже, чем передача одной ссылки на объект класса;
- размер стека ограничен: около мегабайта у вторичных потоков и восемь мегабайт у главного. Большие буферы там держать нельзя, глубокая рекурсия ведёт к переполнению;
- копирование при записи сглаживает картину:
Array,StringиDictionaryэто структуры, но их содержимое лежит в куче, и копия делается только при изменении, если владелец не один; - решает компилятор: значимый тип уезжает в кучу, если он поле класса, захвачен убегающим замыканием, упакован в протокольный тип или лежит в косвенном перечислении. Обратное тоже верно: оптимизатор умеет размещать на стеке объект, не покидающий функцию.
Практический вывод: выбирайте структуру или класс по семантике (нужна копия или общая ссылка, нужна ли идентичность и наследование), а не по предполагаемой скорости. Если производительность действительно важна, её измеряют инструментами, а не выводят из правила.
Где спотыкаются
Уверяют, что структуры всегда на стеке.
Делают огромные структуры ради «скорости» и получают дорогое копирование на каждой передаче.
Забывают про цену ARC в горячем цикле: там иногда выигрывает как раз значимый тип, но проверять это нужно замером.