2. Оптимизации и устройство массивов
Изучение данного блока предполагает предварительное знание синтаксиса языка Swift. Для успешного освоения этого материала, необходимо иметь базовое понимание синтаксиса языка Swift. Это включает в себя знание основных структур данных, операторов, циклов, функций, абстракций и других ключевых элементов языка. Без этих фундаментальных знаний будет сложно понять более сложные концепции и примеры, которые будут рассматриваться в данном блоке.
Массивы в Swift разработаны с учетом высокой производительности и эффективности использования памяти. Под капотом они используют ряд оптимизаций и особенностей реализации, которые позволяют им быть быстрыми и удобными в использовании. В этом разделе мы подробно рассмотрим, как массивы устроены под капотом, какие оптимизации применяются, и как это влияет на производительность, подкрепив теорию практическими примерами.
Copy-on-Write (COW)¶
Одной из ключевых оптимизаций массивов в Swift является механизм Copy-on-Write (COW). Этот механизм позволяет отложить копирование содержимого массива до тех пор, пока это не станет необходимым. Когда вы присваиваете один массив другому, они оба ссылаются на одну и ту же область памяти. Копирование происходит только в случае изменения одного из массивов.
var arrayA = [1, 2, 3]
var arrayB = arrayA // arrayA и arrayB ссылаются на один буфер
arrayB.append(4) // arrayB теперь имеет свой собственный буфер
print(arrayA) // [1, 2, 3]
print(arrayB) // [1, 2, 3, 4]
- При создании arrayA выделяется буфер, содержащий элементы [1, 2, 3].
- При присваивании arrayB = arrayA оба массива ссылаются на один и тот же буфер, и счетчик ссылок на этот буфер равен 2.
- При попытке изменить arrayB через
append(4), механизм COW обнаруживает, что буфер разделяется между двумя массивами, и создает копию буфера специально для arrayB. Теперь arrayB имеет свой собственный буфер, который может быть изменен без влияния на arrayA.
Преимущества COW¶
- Пока массивы не изменяются, они используют один и тот же буфер, что снижает потребление памяти.
- Избегание лишних копирований ускоряет выполнение программы.
Важные моменты¶
- Копирование буфера происходит только при попытке изменить массив.
- Если массив имеет уникальную ссылку на буфер (никто больше не использует этот буфер), копирование не происходит.
Управление памятью и емкостью массива¶
Массивы в Swift управляют своей емкостью (capacity) динамически. Они резервируют определенное количество памяти под элементы, что позволяет добавлять новые элементы без частого перераспределения памяти.
var numbers = [Int]()
numbers.reserveCapacity(1000)
- reserveCapacity(_:) сообщает массиву заранее выделить память под указанное количество элементов.
- Это снижает количество перераспределений памяти при добавлении большого количества элементов.
Геометрическое увеличение емкости¶
При переполнении буфера массив обычно удваивает свою емкость.
Это обеспечивает амортизированную временную сложность операций добавления за O(1).
var numbers = [Int]()
for i in 1...10000 {
numbers.append(i)
}
Иммутабельность и оптимизации¶
Если массив объявлен как let, он считается неизменяемым, и компилятор может применять дополнительные оптимизации.
- Так как массив не может быть изменен, нет необходимости в проверках уникальности буфера.
- Компилятор может разместить содержимое массива в статической памяти, что ускоряет доступ и снижает нагрузку на кучу.
Специализация для типов значений¶
Swift компилируется с использованием Generic Specialization, что позволяет оптимизировать код для конкретных типов.
let intArray = [Int](repeating: 0, count: 1000)
- Компилятор знает, что Int является типом значения и может применять оптимизации для массивов Int.
- Это включает в себя использование низкоуровневых операций для копирования и инициализации элементов.
Inline Storage для маленьких массивов¶
Swift может оптимизировать хранение небольших массивов, используя Inline Storage, где элементы массива хранятся непосредственно внутри структуры массива, а не в отдельной области памяти.
- Нет необходимости выделять память в куче для небольших массивов.
- Доступ к элементам происходит быстрее, так как они находятся рядом в памяти.
Bridging с Objective-C и NSArray¶
Swift массивы автоматически бриджируются (bridged) с NSArray из Objective-C, что позволяет интегрировать код на Swift с существующим кодом на Objective-C.
- Swift массивы могут быть преобразованы в NSArray и наоборот.
- Это происходит автоматически, но может потребовать дополнительных ресурсов.
Оптимизации при бриджинге¶
Если массив содержит объекты, наследуемые от NSObject, и не использует типы значения, бриджинг может быть выполнен без копирования элементов.
import Foundation
let swiftArray = ["One", "Two", "Three"]
let nsArray: NSArray = swiftArray as NSArray
if let backToSwiftArray = nsArray as? [String] {
print(backToSwiftArray)
}
ArraySlice и экономия памяти¶
ArraySlice представляет собой срез массива, который использует ту же область памяти, что и исходный массив, без копирования элементов.
let numbers = [10, 20, 30, 40, 50]
let slice = numbers[1...3] // ArraySlice<Int>
for number in slice {
print(number)
}
- slice использует ту же область памяти, что и numbers.
- Это экономит память и ускоряет операции с подмассивами.
Важные моменты¶
- Индексы slice совпадают с индексами в исходном массиве.
- Если необходимо создать независимый массив, можно преобразовать ArraySlice в Array
let newArray = Array(slice)