Каждый Kotlin-разработчик знает мантру: "Если цепочка операторов длинная, бери Sequence, чтобы избежать промежуточных коллекций".
Это правда. Но часто на код-ревью я вижу
asSequence() на списках из 50-100 элементов. И вот тут начинается оверхед, о котором забывают.В чем подвох?
Операторы над
Iterable (map, filter) - это inline functions.Когда вы пишете
list.filter { ... }, компилятор фактически вставляет код цикла for прямо в место вызова. JIT это обожает, процессор счастлив, аллокаций минимум (кроме результирующего списка).Операторы над
Sequence, это создание объектов.Вызов
.map { ... } создает новый инстанс TransformingSequence. Вызов .filter { ... } - FilteringSequence.Это цепочка объектов-декораторов. Каждый элемент протаскивается через виртуальные вызовы
next().📉 Бенчмарки (JMH, List vs Sequence):
Допустим, у нас простая цепочка:
filter + map + first.1. Маленький список (10-100 элементов):
🔵
Iterable: Быстрее в 1.5 - 2 раза.🔵Причина: создание объектов-оберток для Sequence стоит дороже, чем тупой проход циклом и создание мелких промежуточных ArrayList'ов.
2. Средний список (~1 000 элементов):
🔵Паритет. Разница становится невидимой.
3. Гигантский список (100k+ элементов) или тяжелые операции:
🔵
Sequence: Win. Здесь ленивость спасает от OutOfMemory и лишней работы.💡 Не ставьте
asSequence() по дефолту везде.1. Если коллекция маленькая (помещается на экран телефона) - Iterable.
2. Если вы делаете
map, а потом сразу toList() - Iterable (Sequence тут просто лишний посредник).3. Если стриминг данных, чтение файла построчно или поиск первого совпадения в огромном массиве - Sequence.
Оптимизация должна быть осознанной, а не рефлекторной.
А вы используете
Sequence по дефолту или только там, где профайлер подсветил? 👇✍️ @kotlin_lib