Статьи

Петли производительности в Groovy

Вступление

В 2018 году в рамках «Адвента кодекса» я решил все головоломки в Groovy . Совершенно очевидно, что выбор хорошей структуры данных является наиболее важным для получения эффективного решения. Однако способ итерации по этим структурам также очень важен, по крайней мере, при использовании Groovy.

Измерение производительности

Я хочу измерить, сколько нужно времени, чтобы сложить некоторые числа. Для тестирования производительности циклов я подготовил небольшую функцию, которая просто суммирует некоторые числа:

1
2
3
4
5
void printAddingTime(String message, long to, Closure<Long> adder) {
    LocalTime start = LocalTime.now()
    long sum = adder(to)
    println("$message: $sum calculated in ${Duration.between(start, LocalTime.now()).toMillis()} ms")
}

Псевдокод для суммирования функций приведен ниже:

1
2
3
4
5
for i = 1 to n
  for j = 1 to n
    sum += i * j
  end
end

Типы петель

Давайте реализуем функцию суммирования различными способами.

collect и sum

Первый тип цикла должен использовать встроенную (Groovy) функцию collect и sum коллекций ( Range это в этом примере):

1
2
3
4
5
(1..n).collect { long i ->
  (1..n).collect { long j ->
    i * j
  }.sum()
}.sum()

each

Далее, давайте напишем ту же функцию, используя each встроенную функцию для коллекций ( Range ее в этом примере), а затем добавим результаты в переменную аккумулятора:

1
2
3
4
5
6
7
long sum = 0
(1..n).each { long i ->
    (1..n).each { long j ->
        sum += i * j
    }
}
return sum

times

Теперь вместо использования each мы могли бы использовать функцию times встроенную в Number by Groovy:

1
2
3
4
5
6
7
long sum = 0
n.times { long i ->
  n.times { long j ->
    sum += (i + 1)*(j+1)
  }
}
return sum

Мы должны добавить 1 к i и j потому что времена генерируют числа от 0 до n эксклюзивно.

LongStream с sum

В Java 8 появилась новая функция — потоки. Одним из примеров потоков является LongStream . К счастью, он имеет встроенную функцию sum , которую мы можем использовать:

1
2
3
4
5
LongStream.range(0, n).map { i ->
    LongStream.range(0, n).map { j ->
        (i + 1) * (j + 1)
    }.sum()
}.sum()

LongStream генерирует числа так же, как функция times , поэтому мы также должны добавить 1 к i и j здесь.

LongStream с ручной суммой

Вместо функции sum в LongStream мы можем добавить все числа вручную:

1
2
3
4
5
6
7
long sum = 0
LongStream.range(0, n).forEach { i ->
    LongStream.range(0, n).forEach { j ->
        sum += (i + 1) * (j + 1)
    }
}
return sum

while

Конечно, поскольку Groovy наследует от Java большую часть своего синтаксиса, мы можем использовать цикл while:

01
02
03
04
05
06
07
08
09
10
11
long sum = 0
long i = 1
while(i <= n){
    long j = 1
    while(j <= n){
        sum+= i*j
        ++j
    }
    ++i
}
return sum

for

Как мы можем использовать while , мы также можем использовать цикл for в Groovy:

1
2
3
4
5
6
7
long sum = 0
for (long i = 1; i <= n; ++i) {
    for (long j = 1; j <= n; ++j) {
        sum += i * j
    }
}
return sum

Полученные результаты

Мои тесты я запускаю на Java 1.8 и Groovy 2.5.5 . Скрипт loops.groovy был запущен с использованием скрипта bash:

1
2
3
4
5
6
#!/bin/sh
for x in 10 100 1000 10000 100000; do
  echo $x
  groovy loops.groovy $x
  echo
done

Значения в миллисекундах

Петля н 10 100 1000 10000 100000
collect + sum 7 22 216 16244 1546822
each 12 17 118 7332 706781
times 2 10 109 8264 708684
LongStream + sum 7 17 127 7679 763341
LongStream + ручная сумма 18 35 149 6857 680804
while 8 20 103 3166 301967
for 7 10 25 359 27966

Как вы можете заметить, для небольшого количества итераций достаточно использовать встроенные функции Groovy, но для гораздо большего количества итераций мы должны использовать циклы while или for как в простой старой Java.

Покажи мне код

Код для этих примеров доступен здесь . Вы можете запустить эти примеры на своей машине и проверить производительность самостоятельно.

Смотреть оригинальную статью здесь: Производительность циклов в Groovy

Мнения, высказанные участниками Java Code Geeks, являются их собственными.