1 /*
   2  * Copyright (c) , Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package org.openjdk.bench.java.util.stream.tasks.PhoneCode;
  25 
  26 import org.openjdk.jmh.annotations.Benchmark;
  27 import org.openjdk.jmh.annotations.BenchmarkMode;
  28 import org.openjdk.jmh.annotations.Mode;
  29 import org.openjdk.jmh.annotations.OutputTimeUnit;
  30 import org.openjdk.jmh.annotations.Scope;
  31 import org.openjdk.jmh.annotations.State;
  32 
  33 import java.util.Collection;
  34 import java.util.Collections;
  35 import java.util.HashSet;
  36 import java.util.concurrent.TimeUnit;
  37 import java.util.function.Function;
  38 import java.util.stream.Collectors;
  39 import java.util.stream.IntStream;
  40 import java.util.stream.Stream;
  41 
  42 import static org.openjdk.bench.java.util.stream.tasks.PhoneCode.PhoneCodeProblem.wordsForNumber;
  43 
  44 /**
  45  * This benchmark compare various strategies solving the phone code problem.
  46  * The result should offer some insights on strength/drawbacks of underlying
  47  * implementation.
  48  */
  49 @BenchmarkMode(Mode.Throughput)
  50 @OutputTimeUnit(TimeUnit.MINUTES)
  51 @State(Scope.Benchmark)
  52 public class Bulk {
  53     // several method choke up with 6-digit problem
  54     private final static int SIZE = 5;
  55     private Stream<String> join(String head,
  56                                 String tail,
  57                                 Function<String, Stream<String>> encoder)
  58     {
  59         Stream<String> s = wordsForNumber(head).stream();
  60 
  61         if (! tail.isEmpty()) {
  62             s = s.flatMap(h -> encoder.apply(tail).map(t -> h + " " + t));
  63         }
  64 
  65         return s;
  66     }
  67 
  68     private Stream<String> encode_par1(String number) {
  69         return IntStream.range(1, number.length() + 1)
  70                       .parallel()
  71                       .boxed()
  72                       .flatMap(i -> join(number.substring(0, i),
  73                               number.substring(i),
  74                               this::encode_par1));
  75     }
  76 
  77     private Stream<String> encode_par2(String number) {
  78         return IntStream.range(1, number.length() + 1)
  79                       .boxed()
  80                       .parallel()
  81                       .flatMap(i -> join(number.substring(0, i),
  82                                          number.substring(i),
  83                                          this::encode_par2));
  84     }
  85 
  86     private Stream<String> encode_ser(String number) {
  87         return IntStream.range(1, number.length() + 1)
  88                       .boxed()
  89                       .flatMap(i -> join(number.substring(0, i),
  90                                          number.substring(i),
  91                                          this::encode_ser));
  92     }
  93 
  94     private Stream<String> encode_loop_concat(String number) {
  95         if (number.isEmpty()) {
  96             return Stream.empty();
  97         }
  98         // full number
  99         Stream<String> s = wordsForNumber(number).stream();
 100         // split
 101         for (int i = 1; i < number.length(); i++) {
 102             s = Stream.concat(s, join(number.substring(0, i),
 103                                        number.substring(i),
 104                                        this::encode_loop_concat));
 105         }
 106 
 107         return s;
 108     }
 109 
 110     private Collection<String> encode_loop_collect(String number) {
 111         if (number.isEmpty()) {
 112             return Collections.emptySet();
 113         }
 114 
 115         Collection<String> rv = new HashSet<>();
 116 
 117         for (int i = 1; i <= number.length(); i++) {
 118             join(number.substring(0, i),
 119                  number.substring(i),
 120                  s -> encode_loop_collect(s).stream()).forEach(rv::add);
 121         }
 122 
 123         return rv;
 124     }
 125 
 126     private Collection<String> encode_inline(String number) {
 127         if (number.isEmpty()) {
 128             return Collections.emptySet();
 129         }
 130 
 131         Collection<String> rv = new HashSet<>();
 132 
 133         for (int i = 1; i < number.length(); i++) {
 134             String front = number.substring(0, i);
 135             String rest = number.substring(i);
 136             wordsForNumber(front).stream()
 137                 .flatMap(h -> encode_inline(rest).stream().map(t -> h + " " + t))
 138                 .forEach(rv::add);
 139         }
 140 
 141         rv.addAll(wordsForNumber(number));
 142 
 143         return rv;
 144     }
 145 
 146     @Benchmark
 147     public int bulk_par_range_concurrent() {
 148         // force collect
 149         return PhoneCodeProblem.get(SIZE)
 150                                .flatMap(this::encode_par1)
 151                                .collect(Collectors.toConcurrentMap(
 152                                     Function.identity(),
 153                                     Function.identity(),
 154                                     (l, r) -> l))
 155                                .keySet()
 156                                .size();
 157     }
 158 
 159     @Benchmark
 160     public int bulk_par_boxed_range_concurrent() {
 161         // force collect
 162         return PhoneCodeProblem.get(SIZE)
 163                                .flatMap(this::encode_par2)
 164                                .collect(Collectors.toConcurrentMap(
 165                                        Function.identity(),
 166                                        Function.identity(),
 167                                        (l, r) -> l))
 168                                .keySet()
 169                                .size();
 170     }
 171 
 172     @Benchmark
 173     public int bulk_par_range() {
 174         // force collect
 175         return PhoneCodeProblem.get(SIZE)
 176                                .flatMap(this::encode_par1)
 177                                .collect(Collectors.toSet())
 178                                .size();
 179     }
 180 
 181     @Benchmark
 182     public int bulk_par_boxed_range() {
 183         // force collect
 184         return PhoneCodeProblem.get(SIZE)
 185                                .flatMap(this::encode_par2)
 186                                .collect(Collectors.toSet())
 187                                .size();
 188     }
 189 
 190     @Benchmark
 191     public int bulk_ser_range() {
 192         // force collect
 193         return PhoneCodeProblem.get(SIZE)
 194                                .flatMap(this::encode_ser)
 195                                .collect(Collectors.toSet())
 196                                .size();
 197     }
 198 
 199     @Benchmark
 200     public int bulk_ser_loop_concat() {
 201         // force collect
 202         return PhoneCodeProblem.get(SIZE)
 203                                .flatMap(this::encode_loop_concat)
 204                                .collect(Collectors.toSet())
 205                                .size();
 206     }
 207 
 208     @Benchmark
 209     public int bulk_ser_loop_collect() {
 210         return PhoneCodeProblem.get(SIZE)
 211                                .map(this::encode_loop_collect)
 212                                .map(Collection::size)
 213                                .reduce(0, Integer::sum);
 214     }
 215 
 216     @Benchmark
 217     public int bulk_ser_inline() {
 218         return PhoneCodeProblem.get(SIZE)
 219                                .map(this::encode_inline)
 220                                .map(Collection::size)
 221                                .reduce(0, Integer::sum);
 222     }
 223 }