자바 - 마이크로벤치링: 초보자 가이드

안녕하세요, 미래의 자바 마법사들! ? 오늘은 자바 마이크로벤치링의 세계로 흥미진진한 여정을 떠날 거예요. 아직 코드를 한 줄도 작성해본 적이 없다고 해도 걱정하지 마세요 - 저희는 맨 처음부터 시작하여 함께 자라나가요. 그럼, 커피 한 잔을 준비해요 (차라도 좋으면 차를), 이제 시작해볼까요!

Java - Microbenchmark

마이크로벤치링이란?

자바 마이크로벤치링의 기초부터 배우기 전에, 마이크로벤치링이란 무엇인지 이해해보죠.

셰프로서 레시피를 완벽하게 만들기 위해 노력하는 것을 상상해봅시다. 마지막 요리를 맛볼 때만 좋은지 확인할 것인가요? 아니요, 각 재료를 맛보고, 다른 조리 시간을 시험하고, 다양한 기술을 시도할 거예요. 이는 프로그래밍에서 마이크로벤치링의 정확한 모습입니다 - 코드의 작은, 고립된 부분의 성능을 측정하는 방법입니다.

자바 벤치링의 중요성

이제, "벤치링에 대해 왜 신경 써야 하나?"라고 궁금해할 수 있습니다. 그럼, 살짝 이야기해드릴게요.

저는 젊은 개발자였을 때, 제 컴퓨터에서 완벽하게 작동하는 프로그램을 작성했어요. 하지만 회사의 서버에 배포했을 때, 거북이가 무거운 가방을 들고 다니는 것보다 느려졌어요! 그때부터 벤치링의 중요성을 배웠어요. 이를 통해 우리는 다음을 할 수 있습니다:

  1. 성능 고립 구간을 식별할 수 있습니다
  2. 다른 구현 방법을 비교할 수 있습니다
  3. 우리의 코드가 다른 시스템에서 효율적으로 실행되는지 확인할 수 있습니다

자바 벤치링 기술

일반적인 자바 벤치링 기술을 살펴보죠:

1. 수동 타이밍

가장 간단한 벤치링 방법은 수동 타이밍입니다. 다음은 기본적인 예제입니다:

public class SimpleTimingExample {
public static void main(String[] args) {
long startTime = System.nanoTime();

// 여기에 코드를 작성하세요
for (int i = 0; i < 1000000; i++) {
Math.sqrt(i);
}

long endTime = System.nanoTime();
long duration = (endTime - startTime);
System.out.println("실행 시간: " + duration + " 나노초");
}
}

이 예제에서는 0에서 999,999까지의 숫자의 平方根을 계산하는 데 걸리는 시간을 System.nanoTime()을 사용하여 측정합니다.

2. JMH (Java Microbenchmark Harness) 사용

수동 타이밍은 간단하지만, 항상 정확하지는 않습니다. 이를 위해 JMH가 등장합니다. JMH는.nano/micro/milli/macro 벤치링을 구축하고, 실행하고, 분석하는 데 사용하는 자바 하네스입니다.

JMH를 사용하려면 프로젝트에 추가해야 합니다. Maven을 사용하고 있다면, pom.xml에 다음 의존성을 추가하세요:

<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.35</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.35</version>
</dependency>
</dependencies>

이제 간단한 JMH 벤치링을 작성해보죠:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 3)
public class JMHExample {

@Benchmark
public void benchmarkMathSqrt() {
Math.sqrt(143);
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHExample.class.getSimpleName())
.forks(1)
.build();

new Runner(opt).run();
}
}

이 벤치링은 143의 平方根을 계산하는 데 평균적으로 걸리는 시간을 측정합니다. 어노테이션을 분석해보죠:

  • @BenchmarkMode: 측정할 것을 지정합니다 (이 경우 평균 시간)
  • @OutputTimeUnit: 결과의 단위를 지정합니다
  • @State: "상태" 객체가 공유될 스코프를 정의합니다
  • @Fork: 단일 벤치링을 몇 번 포크할지 지정합니다
  • @Warmup@Measurement: 몇 번의 웜업과 측정 반복을 할지 정의합니다

자바 컬렉션 알고리즘

벤치링에 대해 이야기하면서, 자바 컬렉션 알고리즘에 대해 간단히 설명해볼까요? 이는 프로그램의 성능에 큰 영향을 미칠 수 있는 매우 유용한 도구들입니다.

일부 일반 알고리즘은 다음과 같습니다:

알고리즘 설명 사용 사례
Collections.sort() 리스트를 정렬합니다 요소들을 순서대로 정렬해야 할 때
Collections.binarySearch() 정렬된 리스트를 검색합니다 큰, 정렬된 리스트에서 요소를 찾을 때
Collections.reverse() 리스트를 뒤집습니다 요소들의 순서를 반대로 해야 할 때
Collections.shuffle() 리스트를 임의로 셔플합니다 요소들의 순서를 임의로 랜덤화할 때
Collections.fill() 모든 요소를 지정된 요소로 교체합니다 리스트를 특정 값으로 초기화할 때

이제 Collections.sort()를 사용하여 리스트를 정렬하는 성능을 벤치링해보죠:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Warmup(iterations = 3)
@Measurement(iterations = 3)
public class SortingBenchmark {

@Param({"100", "1000", "10000"})
private int listSize;

private List<Integer> list;

@Setup
public void setup() {
list = new ArrayList<>(listSize);
Random rand = new Random();
for (int i = 0; i < listSize; i++) {
list.add(rand.nextInt());
}
}

@Benchmark
public void benchmarkCollectionsSort() {
Collections.sort(list);
}

public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(SortingBenchmark.class.getSimpleName())
.forks(1)
.build();

new Runner(opt).run();
}
}

이 벤치링은 다른 크기의 리스트(100, 1000, 10000 요소)를 정렬하는 데 걸리는 시간을 측정합니다. 이를 실행하면 리스트의 크기가 증가할 때 정렬 시간이 얼마나 증가하는지 좋은 이해를 얻을 수 있습니다.

결론

그렇게 저희는 자바 마이크로벤치링의 표면을 간지러보았습니다. 기억해두세요, 벤치링은 단지 빠른 코드를 작성하는 것이 아니라, 코드의 성능 특성을 이해하고, 감지된 정보를 바탕으로 결정을 내리는 것입니다.

자바 여정을 계속하면서, 벤치링을 도구箱에 넣어두세요. 소프트웨어 성능의 때로는 격려하는 바다를 탐험할 때, 신뢰할 수 있는 나침반처럼 도움이 될 거예요.

좋은 코딩 되세요, 그리고 벤치링이 항상 통찰력 있게 되세요! ??‍??‍?

Credits: Image by storyset