Memoization

Memoization is a technique to cache results of pure functions. A memoized function behaves as a normal function, but stores the result of previous computations associated with the parameters supplied to produce that result.

The classic example of memoization is Fibonacci:

import arrow.syntax.function.memoize
import kotlin.system.measureNanoTime

fun recursiveFib(n: Long): Long = if (n < 2) {
n
} else {
recursiveFib(n - 1) + recursiveFib(n - 2)
}

fun imperativeFib(n: Long): Long {
return when (n) {
0L -> 0
1L -> 1
else -> {
var a = 0L
var b = 1L
var c = 0L
for (i in 2..n) {
c = a + b
a = b
b = c
}
c
}
}
}

fun main(args: Array<String>) {

var lambdaFib: (Long) -> Long = { it } //Declared ahead to be used inside recursively

lambdaFib = { n: Long ->
if (n < 2) n else lambdaFib(n - 1) + lambdaFib(n - 2)
}

var memoizedFib: (Long) -> Long = { it }

memoizedFib = { n: Long ->
if (n < 2) n else memoizedFib(n - 1) + memoizedFib(n - 2)
}.memoize()

println(milliseconds("imperative fib") { imperativeFib(40) }) //0.006

println(milliseconds("recursive fib") { recursiveFib(40) }) //1143.167
println(milliseconds("lambda fib") { lambdaFib(40) }) //4324.890
println(milliseconds("memoized fib") { memoizedFib(40) }) //1.588
}

inline fun milliseconds(description: String, body: () -> Unit): String {
return "$description:${measureNanoTime(body) / 1_000_000.00} ms"
}

Our memoized version is more than 700 times faster than a recursive function version (which is almost four times faster than the lambda version). The imperative version is unbeatable, as it is heavily optimized by the compiler:

fun main(args: Array<String>) = runBlocking {

var lambdaFib: (Long) -> Long = { it } //Declared ahead to be used inside recursively

lambdaFib = { n: Long ->
if (n < 2) n else lambdaFib(n - 1) + lambdaFib(n - 2)
}

var memoizedFib: (Long) -> Long = { it }

memoizedFib = { n: Long ->
println("from memoized fib n = $n")
if (n < 2) n else memoizedFib(n - 1) + memoizedFib(n - 2)
}.memoize()
val job = launch {
repeat(10) { i ->
launch(coroutineContext) { println(milliseconds("On coroutine $i - imperative fib") { imperativeFib(40) }) }
launch(coroutineContext) { println(milliseconds("On coroutine $i - recursive fib") { recursiveFib(40) }) }
launch(coroutineContext) { println(milliseconds("On coroutine $i - lambda fib") { lambdaFib(40) }) }
launch(coroutineContext) { println(milliseconds("On coroutine $i - memoized fib") { memoizedFib(40) }) }
}
}

job.join()

}

Memoized functions, internally, use a thread-safe structure to store their results, and are therefore safe to use on coroutines or any other concurrent code.

There are potential downsides of using memoized functions. The first is that the process of reading the internal cache is higher than the actual computation or memory consumption, as right now, memoized functions don't expose any behavior to control their internal storage.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.22.164