0Pricing
Swift Academy · Урок

Компромиссы ленивого и немедленного вычисления

Понимайте, когда ленивые вычисления помогают, а когда мешают.

«Компромиссы ленивого и немедленного вычисления» — бесплатный урок Swift Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.

Бесплатных обедов не бывает

Ленивое выполнение не всегда быстрее. Оно заменяет предварительное выделение памяти накладными расходами при каждом доступе и меняет момент выполнения побочных эффектов. Знание этих компромиссов позволяет сделать правильный выбор.

Ленивое выполнение выигрывает: частичное потребление

Нужна только часть огромного преобразования — ленивое выполнение пропускает остальное:

let data = Array(1...1_000_000)
let top = data.lazy.map { $0 * 2 }.prefix(3)
print(Array(top))  // [2, 4, 6]  -- 999,997 transforms skipped

Предварительное вычисление выигрывает: многократное использование

Если вы много раз перебираете результат, предварительное вычисление выполняется один раз, а ленивое выполнение пересчитывает его при каждом проходе:

let doubled = [1, 2, 3].map { $0 * 2 }  // computed once
print(doubled.reduce(0, +))  // 12
print(doubled.max()!)        // 6  -- no recompute

Ленивое выполнение пересчитывает данные

Одно и то же ленивое представление снова выполняет работу при каждой итерации:

var work = 0
let view = [1, 2, 3].lazy.map { (n: Int) -> Int in work += 1; return n }
_ = view.reduce(0, +)
_ = view.max()
print(work)  // 6  -- transformed twice

Накладные расходы на каждый элемент

Каждый ленивый шаг оборачивает последовательность в ещё один тип и вызывает closures для каждого элемента. Для небольших массивов эти расходы могут превысить выигрыш от сэкономленного выделения памяти.

Для небольшого массива подойдёт предварительное вычисление

Если элементов немного и все они используются, выбирайте обычное предварительное вычисление — оно проще, а выделение памяти несущественно:

let nums = [3, 1, 2]
let sorted = nums.map { $0 + 1 }.sorted()
print(sorted)  // [2, 3, 4]

Момент побочных эффектов различается

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

var log: [Int] = []
let v = [1, 2].lazy.map { (n: Int) -> Int in log.append(n); return n }
print(log)        // []  -- nothing ran yet
_ = Array(v)
print(log)        // [1, 2]

Операции, требующие предварительного вычисления

Некоторым операциям в любом случае приходится читать всю последовательность, поэтому ленивое выполнение здесь не даёт преимуществ:

let nums = Array(1...10)
print(nums.lazy.map { $0 * 2 }.sorted())  // sorted must see all -> [2,4,...,20]
print(nums.lazy.map { $0 }.count)         // count walks everything

Ленивое выполнение и contains

Операции с ранним завершением, такие как contains(where:), особенно полезны в ленивом режиме для больших входных данных:

let big = Array(1...1_000_000)
let found = big.lazy.map { $0 * 2 }.contains { $0 == 8 }
print(found)  // true  -- stops at element 4

Краткий список критериев выбора

Используйте ленивое выполнение, когда: источник большой, вы сохраняете только его часть (prefix / first / contains) и перебираете результат один раз. Используйте предварительное вычисление, когда: массив небольшой, результат используется повторно или удобство чтения важнее микрооптимизации.

let huge = Array(1...100_000)
let first = huge.lazy.filter { $0 % 9973 == 0 }.first
print(first!)  // 9973

Измеряйте, а не гадайте

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

let nums = Array(1...50)
let eager = nums.filter { $0 > 25 }.count
let lazyC = nums.lazy.filter { $0 > 25 }.count
print(eager == lazyC)  // true -- same answer, profile for speed

Быстрая проверка

Когда eager лучше выбрать вместо ленивого выполнения?

Итоги

Вы узнали о компромиссах:

  • Ленивое выполнение выигрывает для больших источников при частичном потреблении за один проход
  • Предварительное вычисление выигрывает для небольших массивов и повторно используемых результатов (ленивое выполнение пересчитывает данные)
  • Побочные эффекты в ленивых преобразованиях выполняются позже — не полагайтесь на них
  • Перед оптимизацией проводите измерения

Далее: создание собственных ленивых последовательностей.

Часто задаваемые вопросы

Урок «Компромиссы ленивого и немедленного вычисления» бесплатный?

Да — полный текст урока «Компромиссы ленивого и немедленного вычисления» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.

Чему я научусь в уроке «Компромиссы ленивого и немедленного вычисления»?

Понимайте, когда ленивые вычисления помогают, а когда мешают. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Swift Academy?

Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Компромиссы ленивого и немедленного вычисления»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Swift Academy?

Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Свойство lazy
  2. Ленивые map и filter
  3. Компромиссы ленивого и немедленного вычисления
  4. Создание собственных ленивых последовательностей
← Назад к Swift Academy