Kotlin: Otimização de Performance Kotlin Explicada

Última atualização: 2026-08-26

Performance não é otimização prematura — é fazer as escolhas certas: Charlie substitui cadeias de List por Sequence para evitar alocações de coleções intermediárias para milhões de pedidos, usa IntArray em vez de Array<Int> para eliminar boxing, e valida cada otimização com JMH.

1. O que Você Aprenderá


2. A História Real de um Arquiteto

(1) Problema: Out of Memory Processando um Milhão de Pedidos

O OrderProcessor do Charlie criava uma nova coleção a cada passo ao processar um milhão de pedidos — pico de memória atingiu 4GB, pausas de GC chegaram a 2 segundos.

(2) Solução de Otimização de Performance

KOTLIN
// Antes: 3 coleções intermediárias, 4GB de pico de memória
orders.map { enrich(it) }.filter { it.total > 100 }.groupBy { it.region }

// Depois: 0 coleções intermediárias, 200MB de pico de memória
orders.asSequence()
    .map { enrich(it) }
    .filter { it.total > 100 }
    .groupBy { it.region }  // Apenas 1 coleção final

Avaliação preguiçosa com Sequence + arrays de tipos primitivos + ajuste de pool de corrotinas = 95% de redução de memória, 10x melhoria de throughput.


3. Funções Inline Eliminam Overhead

(1) O Custo Oculto das Lambdas

KOTLIN
// Não-inline: Lambda compila para classe anônima
fun process(order: Order, block: (Order) -> String): String {
    return block(order)  // Cria objeto Function1 a cada chamada
}

// Inline: Código da lambda é inlineado no local da chamada
inline fun process(order: Order, block: (Order) -> String): String {
    return block(order)  // Sem alocação de objeto!
}

(2) Medindo o Efeito do Inline

KOTLIN
// Micro-benchmark JMH (conceitual)
// Não-inline: ~50ns por chamada (alocação de objeto)
// Inline:    ~5ns por chamada (sem alocação, JIT faz inline adicional)

(3) Diretrizes de Uso do Inline

Cenário Recomendação Motivo
Função de ordem superior (corpo de 1-5 linhas) ✅ inline Elimina alocação de objeto lambda
Parâmetro de tipo reified ✅ deve usar inline Preserva tipo em tempo de compilação
Corpo de função grande (>20 linhas) ❌ não usar inline Aumenta tamanho do código
Lambda armazenada/passada adiante ❌ não usar inline Lambda inlineado deixa de existir

4. Seleção de Coleções

(1) Quatro Abordagens de Processamento de Dados

KOTLIN
// 1. List (Eager): cada passo cria nova coleção
val result1 = orders
    .map { enrich(it) }           // Nova List
    .filter { it.total > 100 }    // Nova List
    .toList()                     // Nova List

// 2. Sequence (Lazy): processa um elemento por vez
val result2 = orders.asSequence()
    .map { enrich(it) }
    .filter { it.total > 100 }
    .toList()                     // Apenas 1 List

// 3. Flow (Async Lazy): stream preguiçoso com suporte a suspend
val result3 = orders.asFlow()
    .map { enrichAsync(it) }      // Pode ser suspend
    .filter { it.total > 100 }
    .toList()                     // Apenas 1 List

// 4. Array: menor overhead, tamanho fixo
val result4 = ordersArray
    .map { enrich(it) }           // Cria novo Array
    .filter { it.total > 100 }    // Sem filter em Array

(2) Árvore de Decisão para Seleção de Coleções

100%
flowchart LR
    A[Processamento de Dados] --> B{Precisa de async?}
    B -->|Sim| C[Flow]
    B -->|Não| D{Tamanho dos dados?}
    D -->|Grande >10K| E{Múltiplos passos?}
    D -->|Pequeno <10K| F[List]
    E -->|Sim| G[Sequence]
    E -->|Não| F
    A --> H{Tipos primitivos?}
    H -->|Sim| I[IntArray/DoubleArray]
    H -->|Não| A

(3) Comparação de Performance

Abordagem Memória CPU Melhor Para
List O(n×passos) Baixa (cache-friendly) Dados pequenos, poucos passos
Sequence O(1) Média (pipeline completo por elemento) Dados grandes, muitos passos
Flow O(1) Média + overhead async Fontes de dados assíncronas
Array O(n) Mais baixa (sem boxing) Primitivos, tamanho fixo

5. Arrays de Tipos Primitivos

(1) IntArray vs Array<Int>

KOTLIN
// Array<Int>: cada elemento é um objeto Integer encapsulado (boxed)
val boxed: Array<Int> = Array(1_000_000) { it }
// Memória: ~24MB (4MB dados + 20MB headers de objetos)

// IntArray: int[] primitivo, sem boxing
val primitive: IntArray = IntArray(1_000_000) { it }
// Memória: ~4MB (sem overhead de objeto)

// 6x de economia de memória com arrays primitivos!

(2) Seleção de Array Primitivo

Tipo Array Boxed Array Primitivo Economia
Int Array<Int> IntArray ~6x
Long Array<Long> LongArray ~6x
Double Array<Double> DoubleArray ~6x
Boolean Array<Boolean> BooleanArray ~8x

6. Ajuste de Dispatcher de Corrotinas

(1) Seleção de Dispatcher

KOTLIN
// Default: CPU-bound (paralelismo = núcleos da CPU)
launch(Dispatchers.Default) { sortLargeCollection() }

// IO: I/O bloqueante (até 64 threads por padrão)
launch(Dispatchers.IO) { queryDatabase() }

// Customizado: para padrões específicos de I/O
val orderIoDispatcher = Executors.newFixedThreadPool(32)
    .asCoroutineDispatcher()

// Aumentar tamanho do pool IO
System.setProperty("kotlinx.coroutines.io.parallelism", "128")

(2) Comparação de Ajuste de Dispatcher

Cenário Padrão Após Ajuste Melhoria
64 consultas DB concorrentes IO padrão 64 threads Customizado 128 threads 2x throughput
Computação CPU-intensiva Default 4 núcleos Fixo 8 threads 2x throughput
Carga de trabalho mista IO compartilhado Pools de threads isolados 50% redução de latência

7. Benchmarking com JMH

(1) Configuração JMH

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

(2) Exemplo de Benchmark

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. Exemplo Completo: Otimização de Throughput para Milhão de Pedidos

KOTLIN
// ============================================
// OrderProcessor - Otimização de Performance
// Funcionalidade: 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}")
}

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

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

// Estratégia 3: IntArray (primitivo, sem boxing)
fun processWithIntArray(totals: DoubleArray): Double {
    return totals.sum()
}

// Estratégia 4: Corrotinas (paralelo)
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("Gerando $orderCount pedidos...")
    val orders = generateOrders(orderCount)
    val totalsArray = DoubleArray(orderCount) { orders[it].total }

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

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

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

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

    // Corrotinas
    val coroTime = measureTimeMillis { val r = processWithCoroutines(orders); println("Coroutine resultado: \$$r USD") }
    println("Coroutine tempo: ${coroTime}ms\n")

    // Resumo
    println("=== Resumo da Otimização ===")
    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")
}

Saída (amostra; resultados reais dependem do hardware):

TEXT 📖 Somente leitura
Gerando 1000000 pedidos...

=== Benchmark de Performance ===

List resultado: $4.99995E7 USD
List tempo: 120ms

Sequence resultado: $4.99995E7 USD
Sequence tempo: 45ms

DoubleArray resultado: $5.0E7 USD
DoubleArray tempo: 3ms

Coroutine resultado: $4.99995E7 USD
Coroutine tempo: 35ms

=== Resumo da Otimização ===
  List vs Sequence speedup: 2.7x
  List vs DoubleArray speedup: 40.0x
  List vs Coroutine speedup: 3.4x

9. Exemplos práticos rápidos

▶ Exemplo: Primitivos vs Wrappers (boxing)

KOTLIN
// Primitivos em arrays evitam boxing
fun sumList(): Double {
    val list = List(1_000_000) { it.toDouble() }
    return list.sum()
}

fun sumArray(): Double {
    val array = DoubleArray(1_000_000) { it.toDouble() }
    return array.sum()
}

val n = 5_000_000
val t1 = measureTimeMillis { repeat(100) { sumList() } }
val t2 = measureTimeMillis { repeat(100) { sumArray() } }

println("Lista boxed: ${t1}ms")
println("DoubleArray: ${t2}ms")
println("Speedup: ${t1.toDouble() / t2}")

▶ Exemplo: inline para reduzir overhead

KOTLIN
// Função regular - lambda causa alocações
fun <T> processList(list: List<T>, action: (T) -> Unit) {
    for (item in list) action(item)
}

// inline - sem alocações, ação inlined no caller
inline fun <T> processListInlined(list: List<T>, action: (T) -> Unit) {
    for (item in list) action(item)
}

val items = listOf(1, 2, 3, 4, 5)

// Inline permite it
processListInlined(items) { println("Inline: $it") }

// Saída:
// Inline: 1
// Inline: 2
// ...

▶ Exemplo: Sequence vs List (cálculos preguiçosos)

KOTLIN
fun expensiveOperation(n: Int): Int {
    Thread.sleep(1)
    return n * 2
}

// List - eager: processa todos os itens antes do take
val list = (1..100).map(::expensiveOperation)
val filtered = list.filter { it > 50 }.take(5)

// Sequence - lazy: para após take(5)
val seq = (1..100).asSequence()
    .map(::expensiveOperation)
    .filter { it > 50 }
    .take(5)
    .toList()

println("Primeiros 5: $seq")
println("Evitamos processar: ${100 - seq.size * 10} operações")

// Saída:
// Primeiros 5: [52, 54, 56, 58, 60]
// (Sequence só processa até encontrar os 5)

▶ Exemplo: Object allocation avoidance

KOTLIN
// Singleton ao invés de criar objeto a cada chamada
class LogProcessor {
    private val formatter = StringBuilder()

    fun process(messages: List<String>): String {
        formatter.setLength(0)  // reusa buffer
        messages.forEach { formatter.append(it).append('\n') }
        return formatter.toString()
    }
}

val processor = LogProcessor()
val logs = listOf("Pedido criado", "Pedido confirmado", "Pedido enviado")
println(processor.process(logs))

// Comparar com StringBuilder alocado por chamada
fun processNew(messages: List<String>): String {
    val sb = StringBuilder()  // novo toda vez
    messages.forEach { sb.append(it).append('\n') }
    return sb.toString()
}

println(processNew(logs))

▶ Exemplo: tailrec e redução de stack

KOTLIN
// Sem tailrec - potencial StackOverflow
fun sumLoop(n: Long): Long {
    return if (n == 0L) 0 else n + sumLoop(n - 1)
}

// Com tailrec - otimizado para loop
tailrec fun sumTailrec(n: Long, acc: Long = 0): Long {
    return if (n == 0L) acc else sumTailrec(n - 1, acc + n)
}

val n = 100_000
// sumLoop(n)  // StackOverflowError para n grande
val resultTailrec = sumTailrec(n)
println("Soma até $n: $resultTailrec")

// Saída: Soma até 100000: 5000050000

▶ Exemplo: String templates vs concat

KOTLIN
// Não recomendado - muitas concatenações
fun buildOrderStringOld(id: String, total: Double, customer: String): String {
    var result = ""
    result += "ID: " + id
    result += ", Total: $" + total
    result += " USD, Cliente: " + customer
    return result
}

// String template - mais rápido e legível
fun buildOrderStringNew(id: String, total: Double, customer: String) =
    "ID: $id, Total: \$$total USD, Cliente: $customer"

// StringBuilder para múltiplas concatenações
fun buildLargeReport(rows: List<Pair<String, Double>>): String {
    return buildString {
        append("| ID | Total |\n")
        rows.forEach { (id, total) ->
            append("| $id | \$$total |\n")
        }
    }
}

println(buildOrderStringNew("ORD-001", 299.99, "Alice"))

val rows = listOf("ORD-001" to 299.99, "ORD-002" to 1500.00, "ORD-003" to 50.00)
println(buildLargeReport(rows))

▶ Exemplo: Benchmarking com JMH pattern

KOTLIN
import kotlin.system.measureNanoTime

fun main() {
    // Warmup
    repeat(100_000) { operation1() }

    val n = 1_000_000
    val iterations = 1000

    val t1 = measureNanoTime {
        repeat(iterations) { operation1() }
    }

    val t2 = measureNanoTime {
        repeat(iterations) { operation2() }
    }

    println("Op1: ${t1 / 1_000_000.0}ms total, ${t1 / (iterations * 1_000.0)} μs/op")
    println("Op2: ${t2 / 1_000_000.0}ms total, ${t2 / (iterations * 1_000.0)} μs/op")
}

fun operation1(): Int = (1..100).sum()
fun operation2(): Int {
    var total = 0
    for (i in 1..100) total += i
    return total
}

❓ Perguntas Frequentes

P: Quando devo otimizar a performance? R: Escreva código correto primeiro, meça os gargalos, depois otimize onde importa. Otimização prematura é a raiz de todos os males — mas escolher a estrutura de dados certa (Sequence vs List) não é otimização prematura; é uma decisão de design.

P: Inline aumenta o tamanho do APK/IPA? R: Sim. Cada chamada inlineada copia código para o local da chamada. Funções pequenas e frequentemente chamadas se beneficiam mais do inline. Inline de funções grandes causa inchaço. A biblioteca padrão já equilibra isso bem.

P: Sequence é sempre mais rápida que List? R: Nem sempre. Para dados pequenos (<1000) e operações de passo único, List pode ser mais rápida (cache-friendly, sem overhead de wrapper Sequence). Sequence brilha com 3+ operações em grandes conjuntos de dados.

P: Qual é mais rápido, IntArray ou List<Int>? R: IntArray evita boxing e alocação de objetos — travessia e soma são 5-10x mais rápidas. Mas IntArray não suporta operadores funcionais (map/filter) — você precisará de loops manuais ou conversões.

P: Corrotinas são sempre mais rápidas que threads? R: Não. Para tarefas CPU-bound, corrotinas e threads têm performance similar. Corrotinas se destacam em cenários I/O-bound — lidando com I/O concorrente massivo com poucas threads, evitando overhead de bloqueio de threads.

P: Como medir corretamente a performance do Kotlin? R: Use JMH (Java Microbenchmark Harness). Não use measureTimeMillis para micro-benchmarks — compilação JIT, GC e carregamento de classes distorcem os resultados. JMH cuida de tudo isso automaticamente.


📖 Resumo


📝 Exercícios

  1. Iniciante (⭐): Use measureTimeMillis para comparar o tempo de execução de List vs Sequence em 100.000 elementos. Dica: asSequence()
  2. Intermediário (⭐⭐): Use DoubleArray para computar o valor total de pedidos e compare a performance com List<Double>. Dica: DoubleArray(size) { ... }
  3. Avançado (⭐⭐⭐): Escreva um benchmark JMH formal comparando throughput de List/Sequence/Flow em 1 milhão de pedidos. Dica: @Benchmark + @Setup

← Anterior | Próximo →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%