Почему Iterable.sum () работает медленно в Kotlin? - PullRequest
0 голосов
/ 03 мая 2018

Я заметил удивительную разницу между производительностью Itarable.sum() и прямой для цикла с ручной суммой. Учтите это:

import kotlin.system.measureTimeMillis

fun main(args: Array<String>) {
    var sink = 0;
    repeat(5) {
        println(measureTimeMillis {
            var sum = 0
            for (i in 1..10_000_000) {
                sum += i
            }
            sink += sum
        })
    }
    repeat(5) {
        println(measureTimeMillis {
            sink += (1..10_000_000).sum()
        })
    }
}

Удивительно, но использование Iterable.sum() работает в 10 раз медленнее, по сравнению с кодом, который почти идентичен реализации sum () . Почему это так?

Обновление:

Когда я нацеливаюсь на js, тогда sum () лишь немного медленнее.

measureTimeMillis() можно определить как:

import kotlin.js.Date
public inline fun measureTimeMillis(block: () -> Unit): Double {
    val start = Date.now()
    block()
    return Date.now() - start
}

Update2:

На той же машине Linux, jvm sum () даже медленнее, чем js. Вот результаты для 100_000_000 итераций для jvm (Oracle jdk9) и js (последний chrome):

105   // jvm raw loop
76    // jvm raw loop (jit?)
75    // jvm raw loop (jit?)
75    // jvm raw loop (jit?)
70    // jvm raw loop (jit?)
633   // jvm sum()
431   // jvm sum()
562   // jvm sum()
327   // jvm sum() (jit?)
332   // jvm sum() (jit?)

110   // js raw loop
108   // js raw loop
232   // js raw loop
227   // js raw loop
227   // js raw loop
321   // js sum()
284   // js sum()
264   // js sum()
266   // js sum()
265   // js sum()

Итак, на той же машине jvm работает медленнее, чем js при использовании sum(). Еще один сюрприз.

1 Ответ

0 голосов
/ 03 мая 2018

Очевидно, что мы сравниваем супер-оптимизированные узкие циклы здесь. Я вижу довольно стабильные результаты по повторениям для «ручной суммы» и дикой дисперсии во «встроенном» случае. Это указывает на активность GC.

После запуска VisualVM и использования его плагина VisualGC, я подтвердил, что при ручном вычислении суммы нет активности GC, а во встроенном случае - во многом.

Глядя на сгенерированный байт-код, становится очевидным различие: идиома for (i in 1..range) { ... } компилируется непосредственно в подсчитанный цикл. Это на самом деле задокументировано :

Диапазоны целочисленных типов (IntRange, LongRange, CharRange) имеют дополнительную функцию: их можно повторять. Компилятор позаботится о преобразовании этого аналогично индексируемому циклу for в Java без дополнительных затрат.

К сожалению, та же оптимизация не применяется к функции расширения Iterable.sum(), потому что она должна работать для любого Iterable. Компилятор может увидеть, что происходит, и ввести другую внутреннюю сущность, которая просто преобразует все это в результирующую сумму без вычислений, или использует прямую формулу, если границы диапазона не заданы жестко.

JavaScript находится здесь на том же уровне, потому что он также имеет мощный JIT-компилятор. Я не могу комментировать что-то конкретное, но это, скорее всего, позволяет избежать выделения в горячей петле.

...