From f165f8c91de2a2f9e748803d1a5c75e55abb4144 Mon Sep 17 00:00:00 2001 From: Min Namgung Date: Wed, 17 Jan 2024 23:33:11 +0900 Subject: [PATCH] add: jmh with gradle (#9) --- .../\353\202\250\352\266\201\353\257\274.md" | 221 ++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 "ch05/\353\202\250\352\266\201\353\257\274.md" diff --git "a/ch05/\353\202\250\352\266\201\353\257\274.md" "b/ch05/\353\202\250\352\266\201\353\257\274.md" new file mode 100644 index 0000000..d008d76 --- /dev/null +++ "b/ch05/\353\202\250\352\266\201\353\257\274.md" @@ -0,0 +1,221 @@ +--- +marp: true +--- + +# Gradle 환경에서 JMH 세팅하고 피보나치 성능 측정하기 + +--- + +## Gradle 설정하기 + +build.gradle.kts + +```gradle +plugins { + id("me.champeau.jmh") version "0.7.2" +} +``` + +```gradle +dependencies { + jmh("org.openjdk.jmh:jmh-core:1.36") + jmh("org.openjdk.jmh:jmh-generator-annprocess:1.36") + jmhAnnotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.36") +} +``` + +```gradle +jmh { + fork = 1 + warmupIterations = 1 + iterations = 1 +} +``` + +--- + +## 프로젝트 구조 + +![jmh-dir](https://github.com/Learning-Is-Vital-In-Development/24-optimizing-java-1/assets/75058239/8df20132-aada-4946-af9d-535e4c12d0a3) + +- src/jmh 하위 디렉토리에 벤치마크 코드 작성 + +--- + +## 실행 방법 + +```bash +./gradlew jmh +``` + +혹은 IDE에서 실행 버튼을 눌러서 실행 + +
+ +👉 소스 코드 확인하기 + +--- + +```java +@Fork(warmups = 2, value = 2) +@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(value = Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) +public class FibonacciBenchmark +``` + +- `@Fork`: 측정 반복 횟수 +- `@Warmup`: 측정 전에 JVM을 미리 warmup하는 횟수와 시간 +- `@BenchmarkMode`: 측정 방식 + - `Mode.AverageTime`: 평균 시간 측정 + - `Mode.Throughput`: 초당 처리량 측정 + - `Mode.SampleTime`: 측정 대상 메서드의 실행 시간 측정 (최대/최소 시간 등) + - `Mode.SingleShotTime`: 측정 대상 메서드의 단일 실행 시간 측정 (Cold Start) + - `Mode.All`: 모든 방식으로 측정 +- `@OutputTimeUnit`: 측정 결과를 출력할 시간 단위 +- `@Measurement`: 측정 반복 횟수 + +--- + +그 밖의 어노테이션들 + +- `@State`: Argument 상태 지정 + - `@State(Scope.Thread)`: 쓰레드마다 인스턴스 생성 + - `@State(Scope.Benchmark)`: 벤치마크마다 인스턴스 생성 + - `@State(Scope.Group)`: 같은 그룹에 속한 벤치마크마다 인스턴스 생성 +- `@Threads`: 쓰레드 개수 지정 +- `@Setup`: 측정 전에 실행할 메서드 지정 +- `@TearDown`: 측정 후에 실행할 메서드 지정 +- `@Benchmark`: 측정 대상 메서드로 지정 + +--- + +피보나치 벤치마크 수행 결과 + +```txt +Benchmark Mode Cnt Score Error Units +FibonacciBenchmark.bottomUp avgt 3 50.900 ± 0.649 ns/op +FibonacciBenchmark.bottomUp:·gc.alloc.rate avgt 3 3746.501 ± 45.707 MB/sec +FibonacciBenchmark.bottomUp:·gc.alloc.rate.norm avgt 3 200.000 ± 0.001 B/op +FibonacciBenchmark.bottomUp:·gc.count avgt 3 18.000 counts +FibonacciBenchmark.bottomUp:·gc.time avgt 3 17.000 ms +FibonacciBenchmark.bottomUp:·stack avgt NaN --- +FibonacciBenchmark.memoization avgt 3 99.987 ± 2.166 ns/op +FibonacciBenchmark.memoization:·gc.alloc.rate avgt 3 1907.262 ± 41.570 MB/sec +FibonacciBenchmark.memoization:·gc.alloc.rate.norm avgt 3 200.000 ± 0.001 B/op +FibonacciBenchmark.memoization:·gc.count avgt 3 10.000 counts +FibonacciBenchmark.memoization:·gc.time avgt 3 11.000 ms +FibonacciBenchmark.memoization:·stack avgt NaN --- +FibonacciBenchmark.naiveRecursive avgt 3 2239616569.333 ± 42666708.402 ns/op +FibonacciBenchmark.naiveRecursive:·gc.alloc.rate avgt 3 ≈ 10⁻⁴ MB/sec +FibonacciBenchmark.naiveRecursive:·gc.alloc.rate.norm avgt 3 408.000 ± 0.001 B/op +FibonacciBenchmark.naiveRecursive:·gc.count avgt 3 ≈ 0 counts +FibonacciBenchmark.naiveRecursive:·stack avgt NaN --- +FibonacciBenchmark.stream avgt 3 634.686 ± 18.670 ns/op +FibonacciBenchmark.stream:·gc.alloc.rate avgt 3 2872.475 ± 83.524 MB/sec +FibonacciBenchmark.stream:·gc.alloc.rate.norm avgt 3 1912.000 ± 0.001 B/op +FibonacciBenchmark.stream:·gc.count avgt 3 14.000 counts +FibonacciBenchmark.stream:·gc.time avgt 3 15.000 ms +FibonacciBenchmark.stream:·stack avgt NaN --- +FibonacciBenchmark.tailRecursive avgt 3 26.291 ± 0.311 ns/op +FibonacciBenchmark.tailRecursive:·gc.alloc.rate avgt 3 ≈ 10⁻⁴ MB/sec +FibonacciBenchmark.tailRecursive:·gc.alloc.rate.norm avgt 3 ≈ 10⁻⁵ B/op +FibonacciBenchmark.tailRecursive:·gc.count avgt 3 ≈ 0 counts +FibonacciBenchmark.tailRecursive:·stack avgt NaN --- +``` + +--- + +GCProfiler, StackProfiler 관련 정보들 + +- `gc.alloc.rate`: GC 이벤트로 인한 객체 할당 비율 +- `gc.alloc.rate.norm`: `gc.alloc.rate`를 실행 시간으로 나눈 것 +- `gc.count`: GC 이벤트 횟수 +- `gc.time`: GC 이벤트에 소요된 시간 +- `stack`: 메소드 호출 스택 트레이스 + +--- + +```java +@Fork(warmups = 2, value = 2) +@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(value = Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS) +public class FibonacciBenchmark { + public int fibNaiveRecursive(int x) { + return (x == 1 || x == 2) ? 1 : fibNaiveRecursive(x - 1) + fibNaiveRecursive(x - 2); + } + + public int fibTailRecursive(int x) { + return fibTailRec(x, 0, 1); + } + + private int fibTailRec(int n, int a, int b) { + if (n == 0) return a; + if (n == 1) return b; + return fibTailRec(n - 1, b, a + b); + } + + public int fibMemoization(int x, int[] mem) { + if (mem[x] != 0) return mem[x]; + if (x == 1 || x == 2) return 1; + int n = fibMemoization(x - 1, mem) + fibMemoization(x - 2, mem); + mem[x] = n; + return n; + } + + public int fibBottomUp(int x) { + if (x == 1 || x == 2) return 1; + int[] memory = new int[x + 1]; + memory[1] = 1; + memory[2] = 1; + for (int i = 3; i <= x; i++) memory[i] = memory[i - 1] + memory[i - 2]; + return memory[x]; + } + + public int fibStream(int n) { + return Objects.requireNonNull(Stream.iterate(new Integer[]{0, 1}, s -> new Integer[]{s[1], s[0] + s[1]}) + .limit(n) + .reduce((x, y) -> y).orElse(null))[1]; + } + + @Benchmark + public void naiveRecursive() { + fibNaiveRecursive(45); + } + + @Benchmark + public void tailRecursive(){ + fibTailRecursive(45); + } + + @Benchmark + public void memoization() { + fibMemoization(45, new int[45 + 1]); + } + + @Benchmark + public void bottomUp() { + fibBottomUp(45); + } + + @Benchmark + public void stream() { + fibStream(45); + } + + public static void main(String[] args) throws RunnerException, IOException { + Options opt = new OptionsBuilder() + .include(FibonacciBenchmark.class.getSimpleName()) + .warmupIterations(10) + .measurementIterations(3).forks(1) + .jvmArgs("-server", "-Xms2G", "-Xmx2G") + .addProfiler(GCProfiler.class) + .addProfiler(StackProfiler.class) + .build(); + new Runner(opt).run(); + } +} +```