Комментарии 13
Макс, спасибо за цикл статей. Забавно, что это стандартная библиотека Kotlin и казалось бы все должно быть заоптимизировано...
Смотрю я на все эти ухищрения, для того, чтобы итераторы нормально работали и меня охватывает тоска. Вот поддерживала бы jvm генераторы или разработчики kotlin реализовали бы их сами на уровне генерации байткода(не знаю, насколько это реализуемо, но компилятор C# именно так и делает), столько лишней работы можно было бы избежать. Например, реализация flatten стала бы настолько простой, что даже как-то смешно.
fun <T> Iterable<Iterable<T>>.flatten(): Iterable<T>{
for(iterable in this){
for(item in iterable){
yield item
}
}
}
И производительность при этом проседать не должна, так как по сути при генерации получился бы такой же код, как и в ручной реализации, а при наличии поддержки yield со стороны JVM, ещё бы и прибавку в скорости могли получить.
Ты не совсем верно уловил суть sequences. Это ленивая конструкция, а в твоем примере ты просто перебираешь все элементы подряд.
Код твоего цикла for на самом деле в Java выглядит вот так и он безусловно переберет все элементы за раз.
while (iterable.hasItem()) { iterable.next() }
А в случае sequences вызов каждой итерации по циклу запускается внешним кодом. Сам цикл ленивый и он не перебирает элементы. И все это обернуто в удобные конструкции так, что ты даже не замечаешь, что работаешь теперь не с коллекциями, а с последовательностями.
В случае преобразования коллекций использование sequences дает некоторый выигрыш по скорости. Но основная задача sequences не в этом, а в возможности работать с огромными структурами данных, не загружая их целиком в память.
Например ты можешь через sequences лениво читать файл в 20ГБ по одной строке, при этом снаружи этот код будет выглядеть также, как будто ты работаешь с обычной коллекцией и загрузил весь файл в память. При этом в памяти у тебя всегда будет только одна строка из огромного файла.
csfFile.useLines
.drop(1)
.map { line -> line.toRecord() }
.filter { record -> record.hasAccount }
.forEach { record -> repository.writeRecord(record) }
На самом деле в шарпах тоже полно магии. Линку изнутри сделан на кастомных стейтмашинах, которые мутируют друг в друга, если возможно.
Вот, например, WhereSelectArrayIterator.
Супер, круто сделал!
Единственное при сравнении алгоритмов также следует учитывать разницу в используемой памяти
Согласен, на самом деле я смотрел ее, но по инстансам они равны. В коллекциях на каждое преобразование создается инстанс коллекции, а в сиквенсах инстанс декоратора.
Выигрыш по памяти для сиквенсов очевиден, так как в коллекциях постоянно выделяется и освобождается массив + копирование элементов. И именно за счет исключения этого сиквенсы и выигрывают.
Но вы правы, я не копал глубоко эту тему. Просто поверхностно посмотрел профайлером.
Спасибо за статью! Печально, что на Хабре теперь приходится такие статьи прям выискивать среди «шума».
Оптимизируя sequences — или как мой код попал в kotlin