Slide 1

Slide 1 text

High performance with idiomatic @magdamiu

Slide 2

Slide 2 text

40% of people abandon a website that takes more than 3 seconds to load. 44% of users will tell their friends about a bad experience online 79% of users who are dissatisfied with a website’s performance are less likely to buy from the same site again Source: How Loading Time Affects Your Bottom Line by Neil Patel

Slide 3

Slide 3 text

Table of Contents Performance is User Experience 01 Performance Bottlenecks 02 Measure Performance Issues 03 Idiomatic Kotlin boosts Performance 04

Slide 4

Slide 4 text

Performance is User Experience 01

Slide 5

Slide 5 text

Objective Time vs. Subjective Time

Slide 6

Slide 6 text

We optimize for objective time

Slide 7

Slide 7 text

Just Noticeable Difference (JND) Any human observable phenomena needs to have a certain level of difference in order for that difference to be noticeable.

Slide 8

Slide 8 text

Objective Time differences of 20% or less are imperceptible. Steve Seow, Microsoft

Slide 9

Slide 9 text

Humans tend to overestimate passive waits by 36%. Richard Larson, MIT

Slide 10

Slide 10 text

Performance Bottlenecks 02

Slide 11

Slide 11 text

Class Loader Method Area Heap Java Stacks PC Registers Native Method Stacks Runtime data areas Execution Engine Native Method Interface (JNI) Native method libraries (.dll, .so, etc.) JVM Architecture

Slide 12

Slide 12 text

The garbage collector (GC) finds objects that are not referenced and destroys them. This principle is based on a set of root objects that are always reachable. Impacts of garbage collection: when the integrity of reference trees is difficult to be achieved the stop the world (GC pause) is invoked so the execution of all threads is suspended. Memory management GC Roots Reachable objects Non reachable objects => Garbage

Slide 13

Slide 13 text

Heap fragmentation The memory fragmentation brings two challenges: ● Allocating space for new objects starts to be time consuming because it’s becoming hard to find the next free block of sufficient size ● The unused space between blocks of memory can become so big that the JVM won’t be able to create a new object The solution: to have a compacting step after each GC cycle. Compacting moves all reachable objects to one end of the heap and allocate liniary the memory. The impact: during this process the app is suspended. Fragmented Heap Heap after compacting

Slide 14

Slide 14 text

Resource & Memory leaks ● A resource leak is a situation where a computer program doesn't release the resources it has acquired ○ A scarce resources that have been acquired must be released. Otherwise, an application will suffer from a resource leak, for example, a file handle leak ● A memory leak may happen when an object can't be collected and can't be accessed by running code. The situation when memory that is no longer needed isn't released is referred to as a memory leak. ○ In JVM this thing can happen when a reference to an object that's no longer needed is still stored in another object

Slide 15

Slide 15 text

● Slow rendering is another performance issue that powerfully influences the user experience. ● The device refresh rate of a display is how many times per second the image on the screen can be refreshed ● Frame rate represents how many images software shows per second is the frame rate Slow rendering Update Update Time 16 ms 16 ms 16 ms

Slide 16

Slide 16 text

Measure Performance Issues 03

Slide 17

Slide 17 text

How to measure performance issues Benchmarking General performance metrics

Slide 18

Slide 18 text

Benchmarking Microbenchmarks These are metrics showing the performance of certain functions. Use Java Microbenchmark Harness (JMH) plugin. There is a dedicated repo with Kotlin benchmarks done by JetBrains. Macrobenchmarks These are the opposite of microbenchmarks; test the entire application. Mesobenchmarks These are something in-between, measuring features or workflows.

Slide 19

Slide 19 text

General performance metrics Benchmarking is a small part of performance testing. The main focus of performance testing is checking software: ● Speed: To determine how fast the application responds ● Scalability: To determine the maximum number of users that an application can handle ● Stability: To determine how the application invokes its functions under different loads

Slide 20

Slide 20 text

Performance testing ● Benchmark testing (we're already familiar with this) ● Load testing, which determines how an application works under anticipated user loads ● Volume testing, which tests what happens when a large amount of data populates a database ● Scalability testing, which determines how an application invokes functions with a large number of users ● Stress testing, which involves testing an application under extreme workloads ● Tools to analyse the results of performance testing: JMeter, Firebase Crashlytics, Dynatrace, Grafana & Prometheus

Slide 21

Slide 21 text

Profiling JVM Debugger Memory View Memory View in IntelliJ JProfiler Flame Graphs

Slide 22

Slide 22 text

Idiomatic Kotlin boosts Performance 04

Slide 23

Slide 23 text

Programming Paradigms Declarative Imperative Programming Logic Functional Object-oriented Procedural

Slide 24

Slide 24 text

Functional Programming Key Concepts ● First-class functions ● Immutability ● No side effects ● Function types ● Lambda expressions ● Data classes ● A rich set of APIs for working with objects and collections in a functional style Functional Style in Kotlin

Slide 25

Slide 25 text

val evenNumbers = ArrayList() for(index in 0..numbers.lastIndex) { val currentNumber = numbers[index] if(currentNumber % 2 == 0) { evenNumbers.add(currentNumber) } } val evenNumbers = numbers.filter { it % 2 == 0 } Imperative style Declarative style

Slide 26

Slide 26 text

Double Shift Click “Decompile” Decompiled Java code

Slide 27

Slide 27 text

Functions

Slide 28

Slide 28 text

class Book(val title: String, val author: String) { // impure function fun getBookDetails() = "$title - $author" } // pure function fun getBookDetails(title: String, author: String) = "$title - $author" Pure functions

Slide 29

Slide 29 text

fun titleStartsWithS(book: Book) = book.title.startsWith("S") fun lengthOfTitleGraterThan5(book: Book) = book.title.length > 5 fun authorStartsWithB(book: Book) = book.author.startsWith("B") val book1 = Book("Start with why", "Simon Sinek") val book2 = Book("Dare to lead", "Brene Brown") val books = listOf(book1, book2) val filteredBooks = books .filter(::titleStartsWithS) .filter(::authorStartsWithB) .filter(::lengthOfTitleGraterThan5) High-order functions

Slide 30

Slide 30 text

// dedicated predicat fun allFilters(book: Book): Boolean = titleStartsWithS(book) && lengthOfTitleGraterThan5(book) && authorStartsWithB(book) // anonymous function books.filter(fun(book: Book) = titleStartsWithS(book) && lengthOfTitleGraterThan5(book) && authorStartsWithB(book)) 1st and 2nd possible solutions

Slide 31

Slide 31 text

inline infix fun

((P) -> Boolean).and(crossinline predicate: (P) -> Boolean): (P) -> Boolean { return { p: P -> this(p) && predicate(p) } } books.filter( ::titleStartsWithS and ::authorStartsWithB and ::lengthOfTitleGraterThan5 ) 3rd possible solution: function composition

Slide 32

Slide 32 text

println(books.minByOrNull { it.year }) var priceBooks = 0.0 val prefixPriceDetails = "The current sum is " books.forEach { priceBooks += it.price println("$prefixPriceDetails $priceBooks") } Lambdas expressions & Closure (capturing lambda)

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

class Ref(var value: T) fun main() { val counter = Ref(0) val incrementAction = { counter.value++ } } Class used to simulate capturing a mutable variable An immutable variable is captured, but the actual value is stored in a field and can be changed Capturing a mutable variable

Slide 35

Slide 35 text

public static final void main() { final IntRef counter = new IntRef(); counter.element = 0; Function0 incrementAction = (Function0)(new Function0() { // $FF: synthetic method // $FF: bridge method public Object invoke() { return this.invoke(); } public final int invoke() { IntRef var10000 = counter; int var1; var10000.element = (var1 = var10000.element) + 1; return var1; } }); incrementAction.invoke(); } fun main() { var counter = 0; val incrementAction = { counter++ } incrementAction() } Decompiled Java code

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

Inline

Slide 38

Slide 38 text

public static final void operation(@NotNull Function0 op) { Intrinsics.checkNotNullParameter(op, "op"); String var1 = "Before calling op()"; boolean var2 = false; System.out.println(var1); op.invoke(); var1 = "After calling op()"; var2 = false; System.out.println(var1); } public static final void main() { operation((Function0)null.INSTANCE); } // $FF: synthetic method public static void main(String[] var0) { main(); } fun operation(op: () -> Unit) { println("Before calling op()") op() println("After calling op()") } fun main() { operation { println("This is the actual op function") } }

Slide 39

Slide 39 text

public static final void operation(@NotNull Function0 op) { Intrinsics.checkNotNullParameter(op, "op"); String var1 = "Before calling op()"; boolean var2 = false; System.out.println(var1); op.invoke(); var1 = "After calling op()"; var2 = false; System.out.println(var1); } public static final void main() { operation((Function0)null.INSTANCE); } // $FF: synthetic method public static void main(String[] var0) { main(); } inline fun operation(op: () -> Unit) { println("Before calling op()") op() println("After calling op()") } fun main() { operation { println("This is the actual op function") } } The operation function code will be copied here (bytecode level)

Slide 40

Slide 40 text

inline fun computeValues( number: Int, doubleValue: (number: Int) -> Unit, noinline tripleValue: (number: Int) -> Unit ) { doubleValue.invoke(number) tripleValue.invoke(number) } fun main() { val number = 7; computeValues(number, { println(doubleOfNumber(number)) }, { println(tripleOfNumber(number)) }) } fun doubleOfNumber(number: Int) = 2 * number fun tripleOfNumber(number: Int) = 3 * number public static final void computeValues(int number, @NotNull Function1 doubleValue, @NotNull Function1 tripleValue) { int $i$f$computeValues = 0; Intrinsics.checkNotNullParameter(doubleValue, "doubleValue"); Intrinsics.checkNotNullParameter(tripleValue, "tripleValue"); doubleValue.invoke(number); tripleValue.invoke(number); } public static final void main() { final int number = 7; Function1 tripleValue$iv = (Function1)(new Function1() { // $FF: synthetic method // $FF: bridge method public Object invoke(Object var1) { this.invoke(((Number)var1).intValue()); return Unit.INSTANCE; } public final void invoke(int it) { int var2 = NoinlineSamplesKt.tripleOfNumber(number); boolean var3 = false; System.out.println(var2); } }); int $i$f$computeValues = false; int var4 = false; int var5 = doubleOfNumber(number); boolean var6 = false; System.out.println(var5); tripleValue$iv.invoke(Integer.valueOf(number)); } // $FF: synthetic method public static void main(String[] var0) { main(); } public static final int doubleOfNumber(int number) { return 2 * number; } public static final int tripleOfNumber(int number) { return 3 * number; }

Slide 41

Slide 41 text

inline fun > Iterable.sortedBy(crossinline selector: (T) -> R?): List { return sortedWith(compareBy(selector)) } Inline functions - crossinline

Slide 42

Slide 42 text

inline fun printTypeName() { println(T::class.simpleName) } fun main() { printTypeName() // Int printTypeName() // Char printTypeName() // String println(Int::class.simpleName) // Int println(Char::class.simpleName) // Char println(String::class.simpleName) // String } Reified types public static final void main() { int $i$f$printTypeName = false; String var1 = Reflection.getOrCreateKotlinClass(Integer.class).getSimpleName(); boolean var2 = false; System.out.println(var1); $i$f$printTypeName = false; var1 = Reflection.getOrCreateKotlinClass(Character.class).getSimpleName(); var2 = false; System.out.println(var1); $i$f$printTypeName = false; var1 = Reflection.getOrCreateKotlinClass(String.class).getSimpleName() var2 = false; System.out.println(var1); String var3 = Reflection.getOrCreateKotlinClass(Integer.TYPE).getSimpleName(); boolean var4 = false; System.out.println(var3); var3 = Reflection.getOrCreateKotlinClass(Character.TYPE).getSimpleName(); var4 = false; System.out.println(var3); var3 = Reflection.getOrCreateKotlinClass(String.class).getSimpleName() var4 = false; System.out.println(var3); }

Slide 43

Slide 43 text

fun main() { var counter = 0 repeat(3000) { counter++ } repeat3000 { counter++ } println(counter) } fun repeat3000(action: (Int) -> Unit) { for (index in 0..2999) { action(index) } } public static final void main() { final IntRef counter = new IntRef(); counter.element = 0; short var1 = 3000; boolean var2 = false; boolean var3 = false; int var9 = 0; for(short var4 = var1; var9 < var4; ++var9) { int var6 = false; int var10001 = counter.element++; } repeat3000((Function1)(new Function1() { // $FF: synthetic method // $FF: bridge method public Object invoke(Object var1) { this.invoke(((Number)var1).intValue()); return Unit.INSTANCE; } public final void invoke(int it) { int var10001 = counter.element++; } })); int var8 = counter.element; var2 = false; System.out.println(var8); } // $FF: synthetic method public static void main(String[] var0) { main(); } public static final void repeat3000(@NotNull Function1 action) { Intrinsics.checkNotNullParameter(action, "action"); int index = 0; for(short var2 = 2999; index <= var2; ++index) { action.invoke(index); } } Is there any performance difference?

Slide 44

Slide 44 text

Collections

Slide 45

Slide 45 text

fun main() { measure { smallList() } // 11ms* measure { smallSequence() } // 6ms* } fun smallList() = (0..5) .filter { print("list filter($it) "); it % 2 == 0 } .map { print("list map($it) "); it * it } .first() fun smallSequence() = (0..5) .asSequence() .filter { print("seq filter($it) "); it % 2 == 0 } .map { print("seq map($it) "); it * it } .first() // output list filter(0) list filter(1) list filter(2) list filter(3) list filter(4) list filter(5) list map(0) list map(2) list map(4) 11 ms seq filter(0) seq map(0) 6 ms sequence.filter{...}.map{...}.first() intermediate operation terminal operation Collections vs Sequences

Slide 46

Slide 46 text

Collections vs Sequences Source: Collections and sequences in Kotlin by Florina Muntenescu

Slide 47

Slide 47 text

// works fun List.getNames(): List = this .map { it.name } .filter { it != null } .map { it!! } // better fun List.getNames(): List = this .map { it.name } .filterNotNull() // best fun List.getNames(): List = this .mapNotNull { it.name } Collections: limit the number of operations // filter Collections public inline fun Iterable.filter(predicate: (T -> Boolean): List { return filterTo(ArrayList(), predicate) } // map Collections public inline fun Iterable.map(transform: (T -> R): List { return mapTo(ArrayList(collectionSizeOrDefault(10)), transform) } // map Sequence public fun Sequence.map(transform: (T) -> R) Sequence { return TransformingSequence(this, transform) }

Slide 48

Slide 48 text

Best Practices

Slide 49

Slide 49 text

interface ValueHolder { val value: V } class IntHolder : ValueHolder { override val value: Int get() = Random().nextInt() } fun main() { val sample = IntHolder() println(sample.value) //260078462 println(sample.value) //1657381068 } // immutability by default data class ImmutableKey(val name: String? = null) Immutability

Slide 50

Slide 50 text

interface ValueHolder { val value: V } class IntHolder : ValueHolder { override val value: Int get() = Random().nextInt() } fun main() { val sample = IntHolder() println(sample.value) //260078462 println(sample.value) //1657381068 } // immutability by default data class ImmutableKey(val name: String? = null) Immutability

Slide 51

Slide 51 text

inputStream.use { outputStream.use { // do something with the streams outputStream.write(inputStream.read()) } } // improved option arrayOf(inputStream, outputStream).use { // do something with the streams outputStream.write(inputStream.read()) } // use implementation private inline fun Array.use(block: ()->Unit) { // implementation } Disposable pattern to avoid resource leaks

Slide 52

Slide 52 text

val displayContent = "Hello " + firstName + "! Please confirm that " + email + " is your email address." val displayContentWithTemplate = "Hello $firstName! Please confirm that $email is your email address." StringBuilder / dynamic invocations on JVM 9+ targets

Slide 53

Slide 53 text

class Location(@JvmField var latitude: Double, @JvmField var longitude: Double) public final class Location { @JvmField public double latitude; @JvmField public double longitude; public Location(double latitude, double longitude) { this.latitude = latitude; this.longitude = longitude; } } @JvmField

Slide 54

Slide 54 text

fun > Iterable.countMin(): Int = count { it == this.minOrNull() } fun > Iterable.countMin(): Int { val minValue = this.minOrNull() return count { it == minValue } } Object lifting

Slide 55

Slide 55 text

fun > Iterable.countMin(): Int = count { it == this.minOrNull() } fun > Iterable.countMin(): Int { val minValue = this.minOrNull() return count { it == minValue } } Object lifting

Slide 56

Slide 56 text

fun main(args: Array) { val value = args[0].toIntOrNull() if (value in 0..10) { println(value) } } public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); Integer value = StringsKt.toIntOrNull(args[0]); byte var2 = 0; if (CollectionsKt.contains( (Iterable)(new IntRange(var2, 10)), value)) { System.out.println(value); } } Ranges

Slide 57

Slide 57 text

Ranges

Slide 58

Slide 58 text

fun main(args: Array) { val range = 0..10 val input = 7 if (input in range) { println(input) } } public static final void main(@NotNull String[] args) { Intrinsics.checkNotNullParameter(args, "args"); byte input = 0; IntRange range = new IntRange(input, 10); input = 7; if (range.contains(input)) { boolean var3 = false; System.out.println(input); } } Ranges

Slide 59

Slide 59 text

fun main(args: Array) { val input = args[0].toInt() if (input in 0..10) { println(input) } } public static final void main(@NotNull String[] args) { Intrinsics.checkNotNullParameter(args, "args"); String var2 = args[0]; boolean var3 = false; int input = Integer.parseInt(var2); if (0 <= input) { if (10 >= input) { boolean var4 = false; System.out.println(input); } } } Ranges

Slide 60

Slide 60 text

Be curious. Start with WHY.

Slide 61

Slide 61 text

CREDITS: This presentation template was created by Slidesgo, including icons by Flaticon, infographics & images by Freepik Thanks! @magdamiu magdamiu.com Slides on Twitter

Slide 62

Slide 62 text

Resources ● Benchmark ○ kotlin-benchmarks git repo ○ idea jmh plugin ● Learn more about ○ Idioms ○ Coding conventions ○ Clean Code with Kotlin ● Inspired by ○ Eli.wtf