Далее мой оригинальный ответ, который, как указал @Slaw, на самом деле не отвечал на ваш вопрос (он объяснял, почему Sequence.filter
быстрее, чем Iterable.filter
, а не почему Sequence.filter
, кажется, периодически быстрее чем это обычно). Тем не менее, я оставляю это ниже, поскольку это связано с тем, что я думаю, может быть ответом на ваш актуальный вопрос.
Полагаю, это может быть связано со сборкой мусора. Как вы можете видеть из моего первоначального ответа, когда вы вызываете Iterable.filter
, вы вызываете копирование множества массивов, то есть вы помещаете в память много вещей, которые необходимо очистить в определенные моменты. Интересно, что это за очистка памяти, созданная тестами List
, которая действительно вызывает аномалии? Я думаю, что может случиться так, что сборщик мусора все время включается и выполняет полную сборку: это приводит к замедлению теста List
до более медленного, чем обычно. И после этого запуска память очищается, возможно, поэтому тест Sequence
быстрее в этот раз.
И я подозреваю, что это связано со сборкой мусора, потому что я воспроизвел ваши аномалии, а затем внес одно изменение: вместо вызова testList.filter
я вызываю testList.filterTo
, передавая ArrayList
того же размера, что и список , Это означает, что копирование массива не требуется, а также создание ArrayList
теперь не по времени:
val arrayList = ArrayList<Int>(testList.size)
val listDuration = measureTimeMillis {
testList.filterTo(arrayList) { it % 2 == 0 }.max()
}
Как только я это сделал, аномалии исчезли. Может быть, вы можете проверить в своей системе и посмотреть, не заставит ли это аномалии исчезнуть там же. Это прерывисто, поэтому немного трудно узнать наверняка.
Конечно, это не доказывает, что это сборка мусора, но я думаю, что это делает его возможным виновником. Вы можете включить ведение журнала GC, чтобы узнать, хотите ли вы знать наверняка. Если вы это сделаете, дайте нам знать, что вы найдете: было бы интересно услышать ваши результаты.
Оригинальный ответ ниже (объясняющий, почему Iterable.filter
медленнее Sequence.filter
)
Если вы посмотрите на исходный код Iterable<T>.filter
, вы увидите, что он делает это:
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
Создает новый ArrayList
, затем зацикливает элементы, проверяя предикат для каждого и добавляя их в этот список массивов, если они соответствуют предикату. Это означает, что для каждого элемента X (независимо от размера по умолчанию списка массива) список массивов должен изменить свой размер, чтобы в него можно было добавить больше элементов (т.е. создать новую копию базового массива, в котором хранятся все его данные).
В последовательности код другой:
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
return FilteringSequence(this, true, predicate)
}
Здесь нет некоторого базового массива, хранящего все элементы, поэтому копирование массивов не требуется. Вместо этого есть просто Iterator
, который будет возвращать следующий элемент, который соответствует предикату всякий раз, когда вызывается next
.
Подробно о том, как это реализовано, в классе FilteringSequence
:
internal class FilteringSequence<T>(
private val sequence: Sequence<T>,
private val sendWhen: Boolean = true,
private val predicate: (T) -> Boolean
) : Sequence<T> {
override fun iterator(): Iterator<T> = object : Iterator<T> {
val iterator = sequence.iterator()
var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue
var nextItem: T? = null
private fun calcNext() {
while (iterator.hasNext()) {
val item = iterator.next()
if (predicate(item) == sendWhen) {
nextItem = item
nextState = 1
return
}
}
nextState = 0
}
override fun next(): T {
if (nextState == -1)
calcNext()
if (nextState == 0)
throw NoSuchElementException()
val result = nextItem
nextItem = null
nextState = -1
@Suppress("UNCHECKED_CAST")
return result as T
}