=================
== The Archive ==
=================

JVM 예외 처리 최적화: 스택 트레이스 비용 줄이기

|

Introduction

문제: 스택 트레이스 생성 비용

해결책: 사용자 정의 예외로 스택 트레이스 최적화

1
2
3
4
5
6
7
8
class CustomException(
    message: String? = null,
    cause: Throwable? = null,
) : Exception(message, cause) {
    constructor(cause: Throwable) : this(null, cause)

    override fun fillInStackTrace(): Throwable = this
}

성능 비교: 벤치마크 테스트

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package benchmark

import kotlinx.benchmark.Benchmark
import kotlinx.benchmark.BenchmarkMode
import kotlinx.benchmark.BenchmarkTimeUnit
import kotlinx.benchmark.Blackhole
import kotlinx.benchmark.Measurement
import kotlinx.benchmark.Mode
import kotlinx.benchmark.OutputTimeUnit
import kotlinx.benchmark.Scope
import kotlinx.benchmark.State
import kotlinx.benchmark.Warmup
import kotlin.random.Random

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = 10, time = 500, timeUnit = BenchmarkTimeUnit.NANOSECONDS)
@Measurement(iterations = 100, time = 1, timeUnit = BenchmarkTimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
class ExceptionBenchmark {
    class CustomException(
        message: String? = null,
        cause: Throwable? = null,
    ) : Exception(message, cause) {
        constructor(cause: Throwable) : this(null, cause)

        override fun fillInStackTrace(): Throwable = this
    }

    class RegularException(
        message: String? = null,
        cause: Throwable? = null,
    ) : Exception(message, cause) {
        constructor(cause: Throwable) : this(null, cause)
    }

    @Benchmark
    fun customExceptionCaught() {
        try {
            try {
                check(Random.nextBoolean())
            } catch (e: IllegalStateException) {
                throw CustomException("Custom exception occurred!")
            }
        } catch (e: CustomException) {
            // Do nothing
        }
    }

    @Benchmark
    fun regularExceptionCaught() {
        try {
            try {
                check(Random.nextBoolean())
            } catch (e: IllegalStateException) {
                throw RegularException("Regular exception occurred!")
            }
        } catch (e: RegularException) {
            // Do nothing
        }
    }

    @Benchmark
    fun throwAndCatchCustomException(blackhole: Blackhole) {
        try {
            throw CustomException("Custom exception occurred!")
        } catch (e: CustomException) {
            blackhole.consume(e)
        }
    }

    @Benchmark
    fun throwAndCatchRegularException(blackhole: Blackhole) {
        try {
            throw RegularException("Regular exception occurred!")
        } catch (e: RegularException) {
            blackhole.consume(e)
        }
    }

    @Benchmark
    fun throwCustomExceptionAndUnwindStackTrace(blackhole: Blackhole) {
        try {
            throw CustomException("Custom exception occurred!")
        } catch (e: CustomException) {
            blackhole.consume(e.fillInStackTrace())
        }
    }

    @Benchmark
    fun throwRegularExceptionAndUnwindStackTrace(blackhole: Blackhole) {
        try {
            throw RegularException("Regular exception occurred!")
        } catch (e: RegularException) {
            blackhole.consume(e.fillInStackTrace())
        }
    }
}

벤치마크 결과

main summary:
Benchmark                                                    Mode  Cnt     Score      Error  Units
ExceptionBenchmark.customExceptionCaught                     avgt  100  2703.668 ± 2097.856  ns/op
ExceptionBenchmark.regularExceptionCaught                    avgt  100  4432.898 ± 3822.089  ns/op
ExceptionBenchmark.throwAndCatchCustomException              avgt  100   372.947 ±  456.352  ns/op
ExceptionBenchmark.throwAndCatchRegularException             avgt  100  1941.444 ±  625.122  ns/op
ExceptionBenchmark.throwCustomExceptionAndUnwindStackTrace   avgt  100   341.263 ±  299.158  ns/op
ExceptionBenchmark.throwRegularExceptionAndUnwindStackTrace  avgt  100  3484.684 ± 1228.608  ns/op

단점: 디버깅의 어려움

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package exception

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.io.IOException

// 최적화된 사용자 정의 예외
class OptimizedException(
    message: String? = null,
    throwable: Throwable? = null,
) : Exception(message, throwable) {
    constructor(throwable: Throwable) : this(null, throwable)

    override fun fillInStackTrace(): Throwable = this
}

// 일반적인 예외를 사용하는 함수
fun regularExceptionExample() {
    try {
        throwRegularException()
    } catch (e: IOException) {
        println("Regular Exception:")
        e.printStackTrace()
    }
}

fun throwRegularException(): Unit = throw IOException("Regular IO Exception")

// 최적화된 예외를 사용하는 함수
fun optimizedExceptionExample() {
    try {
        throwOptimizedException()
    } catch (e: OptimizedException) {
        println("Optimized Exception:")
        e.printStackTrace()
    }
}

fun throwOptimizedException(): Unit = throw OptimizedException("Optimized Exception")

// 간접적인 오류 위치를 보여주는 함수
fun indirectErrorExample() {
    try {
        someOperation()
    } catch (e: OptimizedException) {
        println("Indirect Error Example:")
        e.printStackTrace()
    }
}

fun someOperation() {
    anotherOperation()
}

fun anotherOperation(): Unit = throw OptimizedException("Error in another operation")

fun main() =
    runBlocking {
        regularExceptionExample()
        delay(1000)
        println("\n")
        optimizedExceptionExample()
        delay(1000)
        println("\n")
        indirectErrorExample()
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Regular Exception:
java.io.IOException: Regular IO Exception
	at exception.ExceptionOptimizationKt.throwRegularException(ExceptionOptimization.kt:27)
	at exception.ExceptionOptimizationKt.regularExceptionExample(ExceptionOptimization.kt:20)
	at exception.ExceptionOptimizationKt$main$1.invokeSuspend(ExceptionOptimization.kt:59)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:102)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:263)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:95)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:69)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:47)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
	at exception.ExceptionOptimizationKt.main(ExceptionOptimization.kt:58)
	at exception.ExceptionOptimizationKt.main(ExceptionOptimization.kt)

Optimized Exception:
exception.OptimizedException: Optimized Exception

Indirect Error Example:
exception.OptimizedException: Error in another operation

결론

References

Categories:

Tags: