Программирование SwiftARC и памятьРазработчик приложений на Swift

При копировании значения с copy on write хранилищем что происходит с его общим буфером до первой мутации?

При копировании значения с copy-on-write-хранилищем что происходит с его общим буфером до первой мутации?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Исторический контекст

Copy-on-write появился как способ совместить семантику копируемых значений с эффективным использованием памяти. Без него копирование больших массивов, строк или других контейнеров требовало бы немедленно дублировать весь объём данных.

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

Постановка проблемы

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

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

Подробное решение

При копировании value-типа копируется его дескриптор, содержащий ссылку на внутреннее хранилище. Само хранилище не дублируется: ARC увеличивает число сильных ссылок на объект-хранилище.

Перед изменением контейнер проверяет, можно ли безопасно изменить буфер на месте. Если хранилище разделяется несколькими владельцами, создаётся новый буфер, данные копируются в него, и мутация выполняется уже над новой копией. Исходное значение продолжает использовать старый буфер.

var first = [1, 2, 3] var second = first // буфер пока общий second.append(4) // создаётся отдельный буфер print(first) // [1, 2, 3] print(second) // [1, 2, 3, 4]

ARC управляет временем жизни самого внутреннего ссылочного хранилища, но не превращает value-тип в ссылочный. Для пользователя first и second остаются независимыми значениями.

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

Главный компромисс — экономия памяти и времени при копировании против затрат на проверку уникальности и возможное полное копирование при мутации.

Ситуация из практики

Модуль обработки изображений передаёт большой массив пикселей между несколькими этапами пайплайна. Большинство этапов только читает данные, поэтому немедленное копирование буфера создало бы высокий расход памяти.

Вариант с общим всегда изменяемым буфером быстрее, но нарушает изоляцию этапов: изменение изображения на одном этапе может повредить данные другого. Вариант с немедленным копированием безопасен, но создаёт лишние аллокации.

Выбирается copy-on-write-хранилище. Все этапы чтения используют общий буфер, а при первой модификации конкретный этап получает собственную копию. В результате сохраняются value-семантика и приемлемое потребление памяти; пиковая стоимость возникает только там, где действительно нужна независимая модификация.

Что кандидаты часто упускают

  1. Копируются ли элементы-ссылки при copy-on-write-разделении буфера?

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

  1. Почему copy-on-write нельзя реализовать простым общим изменяемым буфером?

Общий изменяемый буфер дал бы ссылочную семантику: две переменные, логически являющиеся копиями значения, наблюдали бы изменения друг друга. Copy-on-write сохраняет общий буфер только до первой мутации и тем самым отделяет оптимизацию хранения от внешней семантики value-типа.

  1. Гарантирует ли завершение copy-on-write-копирования немедленное уменьшение физического потребления памяти?

Нет. После того как старый буфер перестал иметь сильных владельцев, его объект становится доступным для уничтожения, но аллокатор или операционная система могут не вернуть освобождённые страницы процессу немедленно. Поэтому логическое освобождение буфера и уменьшение RSS процесса — разные события.