Skip to content

Commit f939617

Browse files
authored
Merge pull request Kotlin#1161 from Kotlin/flow-performance-improvements
Flow performance improvements and reactive benchmarks
2 parents 997d2f2 + d811d3a commit f939617

35 files changed

+1998
-46
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.2.1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.2.1)
66

77
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
8-
This is a companion version for Kotlin `1.3.30` release.
8+
This is a companion version for Kotlin `1.3.31` release.
99

1010
```kotlin
1111
suspend fun main() = coroutineScope {
@@ -89,7 +89,7 @@ And make sure that you use the latest Kotlin version:
8989

9090
```xml
9191
<properties>
92-
<kotlin.version>1.3.30</kotlin.version>
92+
<kotlin.version>1.3.31</kotlin.version>
9393
</properties>
9494
```
9595

@@ -107,7 +107,7 @@ And make sure that you use the latest Kotlin version:
107107

108108
```groovy
109109
buildscript {
110-
ext.kotlin_version = '1.3.30'
110+
ext.kotlin_version = '1.3.31'
111111
}
112112
```
113113

@@ -133,7 +133,7 @@ And make sure that you use the latest Kotlin version:
133133

134134
```groovy
135135
plugins {
136-
kotlin("jvm") version "1.3.30"
136+
kotlin("jvm") version "1.3.31"
137137
}
138138
```
139139

benchmarks/build.gradle

+45-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,50 @@ 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+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
38+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class"
39+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble//SaneFlowPlaysScrabble\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
40+
41+
// Primes
42+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$2\$1.class"
43+
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$1.class"
44+
}
45+
46+
jmhRunBytecodeGenerator.dependsOn(removeRedundantFiles)
1447

1548
// It is better to use the following to run benchmarks, otherwise you may get unexpected errors:
16-
// ../gradlew --no-daemon cleanJmhJar jmh
49+
// ./gradlew --no-daemon cleanJmhJar jmh -Pjmh="MyBenchmark"
1750
jmh {
51+
jmhVersion = '1.21'
1852
duplicateClassesStrategy DuplicatesStrategy.INCLUDE
1953
failOnError = true
2054
resultFormat = 'CSV'
21-
// include = ['.*ChannelProducerConsumer.*']
55+
if (project.hasProperty('jmh')) {
56+
include = ".*" + project.jmh + ".*"
57+
}
58+
// includeTests = false
2259
}
2360

2461
jmhJar {
@@ -29,8 +66,12 @@ jmhJar {
2966
}
3067

3168
dependencies {
69+
compile "org.openjdk.jmh:jmh-core:1.21"
70+
compile "io.projectreactor:reactor-core:$reactor_vesion"
71+
compile 'io.reactivex.rxjava2:rxjava:2.1.9'
72+
compile "com.github.akarnokd:rxjava2-extensions:0.20.8"
73+
3274
compile "org.openjdk.jmh:jmh-core:1.21"
3375
compile 'com.typesafe.akka:akka-actor_2.12:2.5.0'
3476
compile project(':kotlinx-coroutines-core')
3577
}
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)