Skip to content

Commit c42d338

Browse files
committed
Reactive scrabble benchmarks
* RxJava2 by David Karnok * FlowPlaysScrabbleBase and FlowPlaysScrabbleOpt as flow counterparts * Lower bounds for Flow scrabble benchmark
1 parent a9f8c0d commit c42d338

17 files changed

+1760
-5
lines changed

benchmarks/build.gradle

+44-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/*
22
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
33
*/
4+
sourceCompatibility = 1.8
5+
targetCompatibility = 1.8
46

57
apply plugin: "net.ltgt.apt"
68
apply plugin: "com.github.johnrengelman.shadow"
@@ -10,15 +12,49 @@ repositories {
1012
maven { url "https://repo.typesafe.com/typesafe/releases/" }
1113
}
1214

13-
jmh.jmhVersion = '1.21'
15+
compileJmhKotlin {
16+
kotlinOptions {
17+
jvmTarget = "1.8"
18+
freeCompilerArgs += ['-Xjvm-default=enable']
19+
}
20+
}
21+
22+
/*
23+
* Due to a bug in the inliner it sometimes does not remove inlined symbols (that are later renamed) from unused code paths,
24+
* and it breaks JMH that tries to post-process these symbols and fails because they are renamed.
25+
*/
26+
task removeRedundantFiles(type: Delete) {
27+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
28+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$1.class"
29+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$1.class"
30+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$1.class"
31+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
32+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
33+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
34+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$1\$1.class"
35+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$2\$1.class"
36+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class"
37+
38+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
39+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class"
40+
41+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble//SaneFlowPlaysScrabble\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
42+
43+
}
44+
45+
jmhRunBytecodeGenerator.dependsOn(removeRedundantFiles)
1446

1547
// It is better to use the following to run benchmarks, otherwise you may get unexpected errors:
16-
// ../gradlew --no-daemon cleanJmhJar jmh
48+
// ./gradlew --no-daemon cleanJmhJar jmh -Pjmh="MyBenchmark"
1749
jmh {
50+
jmhVersion = '1.21'
1851
duplicateClassesStrategy DuplicatesStrategy.INCLUDE
1952
failOnError = true
2053
resultFormat = 'CSV'
21-
// include = ['.*ChannelProducerConsumer.*']
54+
if (project.hasProperty('jmh')) {
55+
include = ".*" + project.jmh + ".*"
56+
}
57+
// includeTests = false
2258
}
2359

2460
jmhJar {
@@ -29,8 +65,12 @@ jmhJar {
2965
}
3066

3167
dependencies {
68+
compile "org.openjdk.jmh:jmh-core:1.21"
69+
compile "io.projectreactor:reactor-core:$reactor_vesion"
70+
compile 'io.reactivex.rxjava2:rxjava:2.1.9'
71+
compile "com.github.akarnokd:rxjava2-extensions:0.20.8"
72+
3273
compile "org.openjdk.jmh:jmh-core:1.21"
3374
compile 'com.typesafe.akka:akka-actor_2.12:2.5.0'
3475
compile project(':kotlinx-coroutines-core')
3576
}
36-
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package benchmarks.flow.scrabble;
6+
7+
import benchmarks.flow.scrabble.IterableSpliterator;
8+
import benchmarks.flow.scrabble.ShakespearePlaysScrabble;
9+
import io.reactivex.Flowable;
10+
import io.reactivex.Maybe;
11+
import io.reactivex.Single;
12+
import io.reactivex.functions.Function;
13+
import org.openjdk.jmh.annotations.*;
14+
15+
import java.util.*;
16+
import java.util.Map.Entry;
17+
import java.util.concurrent.TimeUnit;
18+
19+
/**
20+
* Shakespeare plays Scrabble with RxJava 2 Flowable.
21+
* @author José
22+
* @author akarnokd
23+
*/
24+
@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
25+
@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
26+
@Fork(value = 1)
27+
@BenchmarkMode(Mode.AverageTime)
28+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
29+
@State(Scope.Benchmark)
30+
public class RxJava2PlaysScrabble extends ShakespearePlaysScrabble {
31+
32+
@Benchmark
33+
@Override
34+
public List<Entry<Integer, List<String>>> play() throws Exception {
35+
36+
// Function to compute the score of a given word
37+
Function<Integer, Flowable<Integer>> scoreOfALetter = letter -> Flowable.just(letterScores[letter - 'a']) ;
38+
39+
// score of the same letters in a word
40+
Function<Entry<Integer, LongWrapper>, Flowable<Integer>> letterScore =
41+
entry ->
42+
Flowable.just(
43+
letterScores[entry.getKey() - 'a'] *
44+
Integer.min(
45+
(int)entry.getValue().get(),
46+
scrabbleAvailableLetters[entry.getKey() - 'a']
47+
)
48+
) ;
49+
50+
Function<String, Flowable<Integer>> toIntegerFlowable =
51+
string -> Flowable.fromIterable(IterableSpliterator.of(string.chars().boxed().spliterator())) ;
52+
53+
// Histogram of the letters in a given word
54+
Function<String, Single<HashMap<Integer, LongWrapper>>> histoOfLetters =
55+
word -> toIntegerFlowable.apply(word)
56+
.collect(
57+
() -> new HashMap<>(),
58+
(HashMap<Integer, LongWrapper> map, Integer value) ->
59+
{
60+
LongWrapper newValue = map.get(value) ;
61+
if (newValue == null) {
62+
newValue = () -> 0L ;
63+
}
64+
map.put(value, newValue.incAndSet()) ;
65+
}
66+
67+
) ;
68+
69+
// number of blanks for a given letter
70+
Function<Entry<Integer, LongWrapper>, Flowable<Long>> blank =
71+
entry ->
72+
Flowable.just(
73+
Long.max(
74+
0L,
75+
entry.getValue().get() -
76+
scrabbleAvailableLetters[entry.getKey() - 'a']
77+
)
78+
) ;
79+
80+
// number of blanks for a given word
81+
Function<String, Maybe<Long>> nBlanks =
82+
word -> histoOfLetters.apply(word)
83+
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
84+
.flatMap(blank)
85+
.reduce(Long::sum) ;
86+
87+
88+
// can a word be written with 2 blanks?
89+
Function<String, Maybe<Boolean>> checkBlanks =
90+
word -> nBlanks.apply(word)
91+
.flatMap(l -> Maybe.just(l <= 2L)) ;
92+
93+
// score taking blanks into account letterScore1
94+
Function<String, Maybe<Integer>> score2 =
95+
word -> histoOfLetters.apply(word)
96+
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
97+
.flatMap(letterScore)
98+
.reduce(Integer::sum) ;
99+
100+
// Placing the word on the board
101+
// Building the streams of first and last letters
102+
Function<String, Flowable<Integer>> first3 =
103+
word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().limit(3).spliterator())) ;
104+
Function<String, Flowable<Integer>> last3 =
105+
word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().skip(3).spliterator())) ;
106+
107+
108+
// Stream to be maxed
109+
Function<String, Flowable<Integer>> toBeMaxed =
110+
word -> Flowable.just(first3.apply(word), last3.apply(word))
111+
.flatMap(observable -> observable) ;
112+
113+
// Bonus for double letter
114+
Function<String, Maybe<Integer>> bonusForDoubleLetter =
115+
word -> toBeMaxed.apply(word)
116+
.flatMap(scoreOfALetter)
117+
.reduce(Integer::max) ;
118+
119+
// score of the word put on the board
120+
Function<String, Maybe<Integer>> score3 =
121+
word ->
122+
Maybe.merge(Arrays.asList(
123+
score2.apply(word),
124+
score2.apply(word),
125+
bonusForDoubleLetter.apply(word),
126+
bonusForDoubleLetter.apply(word),
127+
Maybe.just(word.length() == 7 ? 50 : 0)
128+
)
129+
)
130+
.reduce(Integer::sum) ;
131+
132+
Function<Function<String, Maybe<Integer>>, Single<TreeMap<Integer, List<String>>>> buildHistoOnScore =
133+
score -> Flowable.fromIterable(() -> shakespeareWords.iterator())
134+
.filter(scrabbleWords::contains)
135+
.filter(word -> checkBlanks.apply(word).blockingGet())
136+
.collect(
137+
() -> new TreeMap<>(Comparator.reverseOrder()),
138+
(TreeMap<Integer, List<String>> map, String word) -> {
139+
Integer key = score.apply(word).blockingGet() ;
140+
List<String> list = map.get(key) ;
141+
if (list == null) {
142+
list = new ArrayList<>() ;
143+
map.put(key, list) ;
144+
}
145+
list.add(word) ;
146+
}
147+
) ;
148+
149+
// best key / value pairs
150+
List<Entry<Integer, List<String>>> finalList2 =
151+
buildHistoOnScore.apply(score3)
152+
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
153+
.take(3)
154+
.collect(
155+
() -> new ArrayList<Entry<Integer, List<String>>>(),
156+
(list, entry) -> {
157+
list.add(entry) ;
158+
}
159+
)
160+
.blockingGet() ;
161+
return finalList2 ;
162+
}
163+
}

0 commit comments

Comments
 (0)