Вступление
В 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, являются их собственными. |