Skip to content

Flow performance improvements and reactive benchmarks #1161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![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)

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

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

```xml
<properties>
<kotlin.version>1.3.30</kotlin.version>
<kotlin.version>1.3.31</kotlin.version>
</properties>
```

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

```groovy
buildscript {
ext.kotlin_version = '1.3.30'
ext.kotlin_version = '1.3.31'
}
```

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

```groovy
plugins {
kotlin("jvm") version "1.3.30"
kotlin("jvm") version "1.3.31"
}
```

Expand Down
49 changes: 45 additions & 4 deletions benchmarks/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
sourceCompatibility = 1.8
targetCompatibility = 1.8

apply plugin: "net.ltgt.apt"
apply plugin: "com.github.johnrengelman.shadow"
Expand All @@ -10,15 +12,50 @@ repositories {
maven { url "https://repo.typesafe.com/typesafe/releases/" }
}

jmh.jmhVersion = '1.21'
compileJmhKotlin {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += ['-Xjvm-default=enable']
}
}

/*
* Due to a bug in the inliner it sometimes does not remove inlined symbols (that are later renamed) from unused code paths,
* and it breaks JMH that tries to post-process these symbols and fails because they are renamed.
*/
task removeRedundantFiles(type: Delete) {
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$2\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$2\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble//SaneFlowPlaysScrabble\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class"

// Primes
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$2\$1.class"
delete "$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$1.class"
}

jmhRunBytecodeGenerator.dependsOn(removeRedundantFiles)

// It is better to use the following to run benchmarks, otherwise you may get unexpected errors:
// ../gradlew --no-daemon cleanJmhJar jmh
// ./gradlew --no-daemon cleanJmhJar jmh -Pjmh="MyBenchmark"
jmh {
jmhVersion = '1.21'
duplicateClassesStrategy DuplicatesStrategy.INCLUDE
failOnError = true
resultFormat = 'CSV'
// include = ['.*ChannelProducerConsumer.*']
if (project.hasProperty('jmh')) {
include = ".*" + project.jmh + ".*"
}
// includeTests = false
}

jmhJar {
Expand All @@ -29,8 +66,12 @@ jmhJar {
}

dependencies {
compile "org.openjdk.jmh:jmh-core:1.21"
compile "io.projectreactor:reactor-core:$reactor_vesion"
compile 'io.reactivex.rxjava2:rxjava:2.1.9'
compile "com.github.akarnokd:rxjava2-extensions:0.20.8"

compile "org.openjdk.jmh:jmh-core:1.21"
compile 'com.typesafe.akka:akka-actor_2.12:2.5.0'
compile project(':kotlinx-coroutines-core')
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package benchmarks.flow.scrabble;

import benchmarks.flow.scrabble.IterableSpliterator;
import benchmarks.flow.scrabble.ShakespearePlaysScrabble;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Single;
import io.reactivex.functions.Function;
import org.openjdk.jmh.annotations.*;

import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
* Shakespeare plays Scrabble with RxJava 2 Flowable.
* @author José
* @author akarnokd
*/
@Warmup(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 7, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(value = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class RxJava2PlaysScrabble extends ShakespearePlaysScrabble {

@Benchmark
@Override
public List<Entry<Integer, List<String>>> play() throws Exception {

// Function to compute the score of a given word
Function<Integer, Flowable<Integer>> scoreOfALetter = letter -> Flowable.just(letterScores[letter - 'a']) ;

// score of the same letters in a word
Function<Entry<Integer, LongWrapper>, Flowable<Integer>> letterScore =
entry ->
Flowable.just(
letterScores[entry.getKey() - 'a'] *
Integer.min(
(int)entry.getValue().get(),
scrabbleAvailableLetters[entry.getKey() - 'a']
)
) ;

Function<String, Flowable<Integer>> toIntegerFlowable =
string -> Flowable.fromIterable(IterableSpliterator.of(string.chars().boxed().spliterator())) ;

// Histogram of the letters in a given word
Function<String, Single<HashMap<Integer, LongWrapper>>> histoOfLetters =
word -> toIntegerFlowable.apply(word)
.collect(
() -> new HashMap<>(),
(HashMap<Integer, LongWrapper> map, Integer value) ->
{
LongWrapper newValue = map.get(value) ;
if (newValue == null) {
newValue = () -> 0L ;
}
map.put(value, newValue.incAndSet()) ;
}

) ;

// number of blanks for a given letter
Function<Entry<Integer, LongWrapper>, Flowable<Long>> blank =
entry ->
Flowable.just(
Long.max(
0L,
entry.getValue().get() -
scrabbleAvailableLetters[entry.getKey() - 'a']
)
) ;

// number of blanks for a given word
Function<String, Maybe<Long>> nBlanks =
word -> histoOfLetters.apply(word)
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
.flatMap(blank)
.reduce(Long::sum) ;


// can a word be written with 2 blanks?
Function<String, Maybe<Boolean>> checkBlanks =
word -> nBlanks.apply(word)
.flatMap(l -> Maybe.just(l <= 2L)) ;

// score taking blanks into account letterScore1
Function<String, Maybe<Integer>> score2 =
word -> histoOfLetters.apply(word)
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
.flatMap(letterScore)
.reduce(Integer::sum) ;

// Placing the word on the board
// Building the streams of first and last letters
Function<String, Flowable<Integer>> first3 =
word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().limit(3).spliterator())) ;
Function<String, Flowable<Integer>> last3 =
word -> Flowable.fromIterable(IterableSpliterator.of(word.chars().boxed().skip(3).spliterator())) ;


// Stream to be maxed
Function<String, Flowable<Integer>> toBeMaxed =
word -> Flowable.just(first3.apply(word), last3.apply(word))
.flatMap(observable -> observable) ;

// Bonus for double letter
Function<String, Maybe<Integer>> bonusForDoubleLetter =
word -> toBeMaxed.apply(word)
.flatMap(scoreOfALetter)
.reduce(Integer::max) ;

// score of the word put on the board
Function<String, Maybe<Integer>> score3 =
word ->
Maybe.merge(Arrays.asList(
score2.apply(word),
score2.apply(word),
bonusForDoubleLetter.apply(word),
bonusForDoubleLetter.apply(word),
Maybe.just(word.length() == 7 ? 50 : 0)
)
)
.reduce(Integer::sum) ;

Function<Function<String, Maybe<Integer>>, Single<TreeMap<Integer, List<String>>>> buildHistoOnScore =
score -> Flowable.fromIterable(() -> shakespeareWords.iterator())
.filter(scrabbleWords::contains)
.filter(word -> checkBlanks.apply(word).blockingGet())
.collect(
() -> new TreeMap<>(Comparator.reverseOrder()),
(TreeMap<Integer, List<String>> map, String word) -> {
Integer key = score.apply(word).blockingGet() ;
List<String> list = map.get(key) ;
if (list == null) {
list = new ArrayList<>() ;
map.put(key, list) ;
}
list.add(word) ;
}
) ;

// best key / value pairs
List<Entry<Integer, List<String>>> finalList2 =
buildHistoOnScore.apply(score3)
.flatMapPublisher(map -> Flowable.fromIterable(() -> map.entrySet().iterator()))
.take(3)
.collect(
() -> new ArrayList<Entry<Integer, List<String>>>(),
(list, entry) -> {
list.add(entry) ;
}
)
.blockingGet() ;
return finalList2 ;
}
}
Loading