404 Not Found

404 Not Found


nginx

Kotlin Performance Optimization Explained

Performance isn't premature optimization — it's making the right choices: Charlie replaces List chains with Sequence to avoid intermediate collection allocations for millions of orders, uses IntArray instead of Array<Int> to eliminate boxing, and validates every optimization with JMH.

1. What You'll Learn


2. An Architect's Real Story

(1) Pain Point: Out of Memory Processing a Million Orders

Charlie's OrderProcessor created a new collection at every step when processing a million orders — peak memory hit 4GB, GC pauses reached 2 seconds.

(2) Performance Optimization Solution

KOTLIN
// Before: 3 intermediate collections, 4GB peak memory
orders.map { enrich(it) }.filter { it.total > 100 }.groupBy { it.region }

// After: 0 intermediate collections, 200MB peak memory
orders.asSequence()
    .map { enrich(it) }
    .filter { it.total > 100 }
    .groupBy { it.region }  // Only 1 final collection

Sequence lazy evaluation + primitive type arrays + coroutine pool tuning = 95% memory reduction, 10x throughput improvement.


3. Inline Functions Eliminate Overhead

(1) The Hidden Cost of Lambdas

KOTLIN
// Non-inline: Lambda compiles to anonymous class
fun process(order: Order, block: (Order) -> String): String {
    return block(order)  // Creates Function1 object every call
}

// Inline: Lambda code is inlined at call site
inline fun process(order: Order, block: (Order) -> String): String {
    return block(order)  // No object allocation!
}

(2) Measuring the Inline Effect

KOTLIN
// JMH micro-benchmark (conceptual)
// Non-inline: ~50ns per call (object allocation)
// Inline:    ~5ns per call (no allocation, JIT inlines further)

(3) Inline Usage Guidelines

Scenario Recommendation Reason
Higher-order function (1-5 line body) ✅ inline Eliminates lambda object allocation
reified type parameter ✅ must inline Preserve type at compile time
Large function body (>20 lines) ❌ don't inline Increases code size
Lambda stored/passed around ❌ don't inline Inlined lambda no longer exists

4. Collection Selection

(1) Four Data Processing Approaches

KOTLIN
// 1. List (Eager): each step creates new collection
val result1 = orders
    .map { enrich(it) }           // New List
    .filter { it.total > 100 }    // New List
    .toList()                     // New List

// 2. Sequence (Lazy): processes one element at a time
val result2 = orders.asSequence()
    .map { enrich(it) }
    .filter { it.total > 100 }
    .toList()                     // Only 1 List

// 3. Flow (Async Lazy): suspend-capable lazy stream
val result3 = orders.asFlow()
    .map { enrichAsync(it) }      // Can be suspend
    .filter { it.total > 100 }
    .toList()                     // Only 1 List

// 4. Array: lowest overhead, fixed size
val result4 = ordersArray
    .map { enrich(it) }           // Creates new Array
    .filter { it.total > 100 }    // No filter on Array

(2) Collection Selection Decision Tree

100%
flowchart LR
    A[Data Processing] --> B{Async needed?}
    B -->|Yes| C[Flow]
    B -->|No| D{Data size?}
    D -->|Large >10K| E{Multiple steps?}
    D -->|Small <10K| F[List]
    E -->|Yes| G[Sequence]
    E -->|No| F
    A --> H{Primitive types?}
    H -->|Yes| I[IntArray/DoubleArray]
    H -->|No| A

(3) Performance Comparison

Approach Memory CPU Best For
List O(n×steps) Low (cache-friendly) Small data, few steps
Sequence O(1) Medium (full pipeline per element) Large data, many steps
Flow O(1) Medium + async overhead Async data sources
Array O(n) Lowest (no boxing) Primitives, fixed size

5. Primitive Type Arrays

(1) IntArray vs Array<Int>

KOTLIN
// Array<Int>: each element is boxed Integer object
val boxed: Array<Int> = Array(1_000_000) { it }
// Memory: ~24MB (4MB data + 20MB object headers)

// IntArray: primitive int[], no boxing
val primitive: IntArray = IntArray(1_000_000) { it }
// Memory: ~4MB (no object overhead)

// 6x memory savings for primitive arrays!

(2) Primitive Array Selection

Type Boxed Array Primitive Array Savings
Int Array<Int> IntArray ~6x
Long Array<Long> LongArray ~6x
Double Array<Double> DoubleArray ~6x
Boolean Array<Boolean> BooleanArray ~8x

6. Coroutine Dispatcher Tuning

(1) Dispatcher Selection

KOTLIN
// Default: CPU-bound (parallelism = CPU cores)
launch(Dispatchers.Default) { sortLargeCollection() }

// IO: blocking I/O (up to 64 threads by default)
launch(Dispatchers.IO) { queryDatabase() }

// Custom: for specific I/O patterns
val orderIoDispatcher = Executors.newFixedThreadPool(32)
    .asCoroutineDispatcher()

// Increase IO pool size
System.setProperty("kotlinx.coroutines.io.parallelism", "128")

(2) Dispatcher Tuning Comparison

Scenario Default After Tuning Improvement
64 concurrent DB queries IO default 64 threads Custom 128 threads 2x throughput
CPU-intensive computation Default 4 cores Fixed 8 threads 2x throughput
Mixed workload Shared IO Isolated thread pools 50% latency reduction

7. JMH Benchmarking

(1) JMH Configuration

KOTLIN
// build.gradle.kts
dependencies {
    implementation("org.openjdk.jmh:jmh-core:1.37")
    implementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
}

(2) Benchmark Example

KOTLIN
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
open class OrderProcessingBenchmark {

    private lateinit var orders: List<Order>

    @Setup
    fun setup() {
        orders = (1..100_000).map {
            Order("ORD-$it", it * 10.0, "CONFIRMED", "Customer-$it")
        }
    }

    @Benchmark
    fun listPipeline(): Map<String, List<Order>> {
        return orders
            .filter { it.total > 1_000 }
            .map { it.copy(total = it.total * 0.9) }
            .groupBy { it.customer }
    }

    @Benchmark
    fun sequencePipeline(): Map<String, List<Order>> {
        return orders.asSequence()
            .filter { it.total > 1_000 }
            .map { it.copy(total = it.total * 0.9) }
            .groupBy { it.customer }
    }
}

8. Complete Example: Million-Order Throughput Optimization

KOTLIN
// ============================================
// OrderProcessor - Performance Optimization
// Feature: Sequence, IntArray, Coroutines, Benchmarking
// ============================================

import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis

data class Order(val id: String, val total: Double, val status: String, val customer: String)

fun generateOrders(count: Int): List<Order> = (1..count).map {
    Order("ORD-$it", it * 0.1, if (it % 5 == 0) "CANCELLED" else "CONFIRMED", "CUST-${it % 100}")
}

// Strategy 1: List (eager)
fun processWithList(orders: List<Order>): Double {
    return orders
        .filter { it.status != "CANCELLED" }
        .map { it.total }
        .sum()
}

// Strategy 2: Sequence (lazy)
fun processWithSequence(orders: List<Order>): Double {
    return orders.asSequence()
        .filter { it.status != "CANCELLED" }
        .map { it.total }
        .sum()
}

// Strategy 3: IntArray (primitive, no boxing)
fun processWithIntArray(totals: DoubleArray): Double {
    return totals.sum()
}

// Strategy 4: Coroutines (parallel)
suspend fun processWithCoroutines(orders: List<Order>, chunkSize: Int = 10_000): Double {
    return coroutineScope {
        orders.chunked(chunkSize)
            .map { chunk -> async(Dispatchers.Default) { chunk.filter { it.status != "CANCELLED" }.sumOf { it.total } } }
            .awaitAll()
            .sum()
    }
}

fun main() = runBlocking {
    val orderCount = 1_000_000
    println("Generating $orderCount orders...")
    val orders = generateOrders(orderCount)
    val totalsArray = DoubleArray(orderCount) { orders[it].total }

    println("\n=== Performance Benchmark ===\n")

    // List
    val listTime = measureTimeMillis { val r = processWithList(orders); println("List result: \$$r USD") }
    println("List time: ${listTime}ms\n")

    // Sequence
    val seqTime = measureTimeMillis { val r = processWithSequence(orders); println("Sequence result: \$$r USD") }
    println("Sequence time: ${seqTime}ms\n")

    // IntArray
    val arrayTime = measureTimeMillis { val r = processWithIntArray(totalsArray); println("DoubleArray result: \$$r USD") }
    println("DoubleArray time: ${arrayTime}ms\n")

    // Coroutines
    val coroTime = measureTimeMillis { val r = processWithCoroutines(orders); println("Coroutine result: \$$r USD") }
    println("Coroutine time: ${coroTime}ms\n")

    // Summary
    println("=== Optimization Summary ===")
    println("  List vs Sequence speedup: ${"%.1f".format(listTime.toDouble() / seqTime)}x")
    println("  List vs DoubleArray speedup: ${"%.1f".format(listTime.toDouble() / arrayTime)}x")
    println("  List vs Coroutine speedup: ${"%.1f".format(listTime.toDouble() / coroTime)}x")
}

Output (sample; actual results depend on hardware):

TEXT
Generating 1000000 orders...

=== Performance Benchmark ===

List result: $4.99995E7 USD
List time: 120ms

Sequence result: $4.99995E7 USD
Sequence time: 45ms

DoubleArray result: $5.0E7 USD
DoubleArray time: 3ms

Coroutine result: $4.99995E7 USD
Coroutine time: 35ms

=== Optimization Summary ===
  List vs Sequence speedup: 2.7x
  List vs DoubleArray speedup: 40.0x
  List vs Coroutine speedup: 3.4x

❓ FAQ

Q When should I optimize performance?
A Write correct code first, measure bottlenecks, then optimize where it matters. Premature optimization is the root of all evil — but choosing the right data structure (Sequence vs List) isn't premature optimization; it's a design decision.
Q Does inline increase APK/IPA size?
A Yes. Every inlined call copies code to the call site. Small, frequently-called functions benefit most from inlining. Large function inlining causes bloating. The standard library already balances this well.
Q Is Sequence always faster than List?
A Not always. For small data (<1000) and single-step operations, List may be faster (cache-friendly, no Sequence wrapper overhead). Sequence shines with 3+ step operations on large datasets.
Q Which is faster, IntArray or List<Int>?
A IntArray avoids boxing and object allocation — traversal and summation are 5-10x faster. But IntArray doesn't support functional operators (map/filter) — you'll need manual loops or conversions.
Q Are coroutines always faster than threads?
A No. For CPU-bound tasks, coroutines and threads perform similarly. Coroutines excel in I/O-bound scenarios — handling massive concurrent I/O with few threads, avoiding thread blocking overhead.
Q How do I properly measure Kotlin performance?
A Use JMH (Java Microbenchmark Harness). Don't use measureTimeMillis for micro-benchmarks — JIT compilation, GC, and class loading all skew results. JMH handles all of these automatically.

📖 Summary


📝 Exercises

  1. Beginner (⭐): Use measureTimeMillis to compare List vs Sequence execution time on 100,000 elements. Hint: asSequence()
  2. Intermediate (⭐⭐): Use DoubleArray to compute total order value and compare performance against List<Double>. Hint: DoubleArray(size) { ... }
  3. Advanced (⭐⭐⭐): Write a formal JMH benchmark comparing List/Sequence/Flow throughput on 1 million orders. Hint: @Benchmark + @Setup

← Previous | Next →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏