Статьи

Пятничный бенчмаркинг функциональной Java

Давайте представим, что наш владелец продукта сходит с ума однажды и просит вас сделать следующее:

From a set of Strings as follows : "marco_8", "john_33", "marco_1", "john_33", "thomas_5", "john_33", "marco_4", .... give me a comma separated String with only the marco's numbers and numbers need to be in order. Example of expected result : "1,4,8"

From a set of Strings as follows : "marco_8", "john_33", "marco_1", "john_33", "thomas_5", "john_33", "marco_4", .... give me a comma separated String with only the marco's numbers and numbers need to be in order. Example of expected result : "1,4,8"

From a set of Strings as follows : "marco_8", "john_33", "marco_1", "john_33", "thomas_5", "john_33", "marco_4", .... give me a comma separated String with only the marco's numbers and numbers need to be in order. Example of expected result : "1,4,8"

From a set of Strings as follows : "marco_8", "john_33", "marco_1", "john_33", "thomas_5", "john_33", "marco_4", .... give me a comma separated String with only the marco's numbers and numbers need to be in order. Example of expected result : "1,4,8"

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

  • Традиционная ява с петлями и все.
  • Функционально с гуавой
  • Функциональный с потоком Java 8
  • Функциональный с Java 8 Параллельный поток

Код ниже или в гисте

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package com.marco.brownbag.functional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.Ordering;
public class MicroBenchMarkFunctional {
 
        private static final int totStrings = 2;
 
        public static void main(String[] args) {
 
                Set<String> someNames = new HashSet<String>();
 
                init(someNames);
 
                for (int i = 1; i < totStrings; i++) {
                        someNames.add("marco_" + i);
                        someNames.add("someone_else_" + i);
                }
 
                System.out.println("start");
 
                run(someNames);
 
        }
 
        private static void run(Set<String> someNames) {
                System.out.println("========================");
                long start = System.nanoTime();
                int totalLoops = 20;
                for (int i = 1; i < totalLoops; i++) {
                        classic(someNames);
                }
                System.out.println("Classic         : " + ((System.nanoTime() - start)) / totalLoops);
 
                start = System.nanoTime();
                for (int i = 1; i < totalLoops; i++) {
                        guava(someNames);
                }
                System.out.println("Guava           : " + ((System.nanoTime() - start)) / totalLoops);
 
                start = System.nanoTime();
                for (int i = 1; i < totalLoops; i++) {
                        stream(someNames);
                }
                System.out.println("Stream          : " + ((System.nanoTime() - start)) / totalLoops);
 
                start = System.nanoTime();
                for (int i = 1; i < totalLoops; i++) {
                        parallelStream(someNames);
                }
                System.out.println("Parallel Stream : " + ((System.nanoTime() - start)) / totalLoops);
 
                System.out.println("========================");
        }
 
        private static void init(Set<String> someNames) {
                someNames.add("marco_1");
                classic(someNames);
                guava(someNames);
                stream(someNames);
                parallelStream(someNames);
                someNames.clear();
        }
 
        private static String stream(Set<String> someNames) {
                return someNames.stream().filter(element -> element.startsWith("m")).map(element -> element.replaceAll("marco_", "")).sorted()
                                .collect(Collectors.joining(","));
        }
 
        private static String parallelStream(Set<String> someNames) {
                return someNames.parallelStream().filter(element -> element.startsWith("m")).map(element -> element.replaceAll("marco_", "")).sorted()
                                .collect(Collectors.joining(","));
        }
 
        private static String guava(Set<String> someNames) {
                return Joiner.on(',').join(
                                Ordering.from(String.CASE_INSENSITIVE_ORDER).immutableSortedCopy(
                                                Collections2.transform(Collections2.filter(someNames, Predicates.containsPattern("marco")), REPLACE_MARCO)));
 
        }
 
        private static Function<String, String> REPLACE_MARCO = new Function<String, String>() {
                @Override
                public String apply(final String element) {
                        return element.replaceAll("marco_", "");
                }
        };
 
        private static String classic(Set<String> someNames) {
 
                List<String> namesWithM = new ArrayList<String>();
 
                for (String element : someNames) {
                        if (element.startsWith("m")) {
                                namesWithM.add(element.replaceAll("marco_", ""));
                        }
                }
 
                Collections.sort(namesWithM);
 
                StringBuilder commaSeparetedString = new StringBuilder();
 
                Iterator<String> namesWithMIterator = namesWithM.iterator();
                while (namesWithMIterator.hasNext()) {
                        commaSeparetedString.append(namesWithMIterator.next());
                        if (namesWithMIterator.hasNext()) {
                                commaSeparetedString.append(",");
                        }
 
                }
 
                return commaSeparetedString.toString();
 
        }
}

Два момента, прежде чем мы углубимся в производительность:

  1. Забудьте о методе init (), он просто инициализирует объекты в jvm, иначе числа просто сумасшедшие.
  2. Функциональный стиль Java 8 выглядит лучше и чище, чем гуава, а затем развиваться традиционным способом!

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

Запуск этой программы на моем Mac с 4 ядрами приводит к следующему:

1
2
3
4
5
6
========================
Classic         : 151941400
Guava           : 238798150
Stream          : 151853850
Parallel Stream : 55724700
========================

Параллельный поток в 3 раза быстрее . Это связано с тем, что java разделит работу на несколько задач (общее количество задач зависит от вашей машины, ядер и т. Д.) И будет выполнять их параллельно, объединяя результаты в конце.

Классический поток Java и Java 8 имеют более или менее одинаковую производительность.

Гуава неудачник.

Это удивительно, поэтому кто-то может подумать: «Круто, я всегда могу использовать ParallelsStream, и у меня будет большой бонус в конце года».

Но жизнь никогда не бывает легкой. Вот что происходит, когда вы уменьшаете этот набор строк с 200.000 до 20:

1
2
3
4
5
6
========================
Classic         : 36950
Guava           : 69650
Stream          : 29850
Parallel Stream : 143350
========================

Параллельный поток стал чертовски медленным. Это потому, что у ParallelsStream большие издержки с точки зрения инициализации и управления многозадачностью и сбора результатов.

Поток Java 8 теперь выглядит победителем по сравнению с другими 2.

Хорошо, в этот момент кто-то может сказать что-то вроде: «для коллекций с большим количеством элементов я использую ParallelsStream, в противном случае я использую Stream».

Это было бы хорошо и просто получить, но что произойдет, когда я снова уменьшу этот сет с 20 до 2?
Этот :

1
2
3
4
5
6
========================
Classic         : 8500
Guava           : 20050
Stream          : 24700
Parallel Stream : 67850
========================

Классические циклы Java быстрее с очень небольшим количеством элементов.

Поэтому в этот момент я могу вернуться к своему сумасшедшему владельцу продукта и спросить, сколько строк он считает в этой коллекции входных данных. 20? Меньше? Больше? намного больше?

Как Плотник говорит: « Измерить дважды, разрезать один раз!