Skip to content

Commit

Permalink
add: jmh with gradle (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmin11 authored Jan 17, 2024
1 parent 2e9af2c commit f165f8c
Showing 1 changed file with 221 additions and 0 deletions.
221 changes: 221 additions & 0 deletions ch05/남궁민.md
Original file line number Diff line number Diff line change
@@ -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에서 실행 버튼을 눌러서 실행

<br>

👉 소스 코드 확인하기

---

```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();
}
}
```

0 comments on commit f165f8c

Please sign in to comment.