2021.12.08 - [자바 & 스프링] - java - String, StringBuilder, StringBuffer
이전에 문자열을 다루는 자바 클래스 String, StringBuilder, StringBuffer에 관한 포스팅을 한 적 이 있다.
이번 글에서는 위 클래스들의 속도와 힙 메모리를 얼마나 차지하는 지를 확인해서 성능비교를 해보겠다.
속도 측정은 JMH (Java Microbenchmark Harness)로 진행했고, 힙 메모리 비교는 util method 커스텀하게 작성하여 진행하였다.
JMH 속도 비교
package compare;
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;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class StringCompareJMH {
final static int iter = 10000;
final static String value = "abcde";
@Benchmark
public void StringTest() {
String a = new String();
for (int i = 0; i < iter; i++) {
a += value;
}
}
@Benchmark
public void StringBuilderTest() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < iter; i++) {
b.append(value);
}
}
@Benchmark
public void StringBuilderWithInitialSizeTest() {
StringBuilder c = new StringBuilder(iter * value.length());
for (int i = 0; i < iter; i++) {
c.append(value);
}
}
@Benchmark
public void StringBufferTest() {
StringBuffer d = new StringBuffer();
for (int i = 0; i < iter; i++) {
d.append(value);
}
}
@Benchmark
public void StringBufferWithInitialSizeTest() {
StringBuffer e = new StringBuffer(iter * value.length());
for (int i = 0; i < iter; i++) {
e.append(value);
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(StringCompareJMH.class.getSimpleName())
.warmupIterations(3)
.measurementIterations(5)
.forks(1)
.build();
new Runner(opt).run();
}
}
결과
benchmark 결과 StringBuilder > StringBuffer > String 순으로 속도가 빨랐다. StringBuilder와 StringBuffer 클래스를 생성할 때 초기값을 준 코드가 기본 Size를 초과했을 때 Size를 1.5배를 확장해서 재할당하는 작업을 하지 않기 때문에 더 빠르다. 기본 String 클래스를 이용해 연산을 수행하면 StringBuilder와 StringBuffer와 비교해서 어마어마하게 느린 것을 볼 수 있다.
Heap Memory 비교
아래의 Util Method로 Heap Memory를 측정했다.
private static Runtime rt = Runtime.getRuntime();
public static long printHeap(int idx) {
rt.gc();
long t = rt.totalMemory();
long f = rt.freeMemory();
long u = t - f;
System.out.printf("%d HEAP: %,8d bytes%n", idx, u);
return u;
}
결과
반복문을 10000번 돌도록 테스트 했지만, 반복 횟수를 더 늘리면 늘릴 수록 메모리 차이가 많이 난다.
StringBuilder와 StringBuffer를 사용할 때 문자열 객체가 꼭 필요한 경우가 아니면 byte 배열로 갖고 있는 것(stringBuilder.toString().getBytes())이 Heap 메모리를 더 줄일 수 있는 방법이다.
결론
- String 보다 StringBuilder를 사용하자, thread-safe 해야 하는 환경이면 StringBuffer를 사용하자
- StringBuilder와 StringBuffer를 사용할 때는 대략적인 문자열 사이즈를 예측해서 초기에 할당해주자
- 파일로 저장하거나 Network 통신을 위해 생성한 String은 byte[] 로 변환해서 더 가볍게 갖고 있자,
원래 문자열 객체에는 null 값을 넣어줌으로 gc 대상이 되게 하자.
참고
- https://www.youtube.com/watch?v=_PerqNUKKjY&t=411s
- https://www.youtube.com/watch?v=_5CH6uwemOs&t=3s
'자바 & 스프링' 카테고리의 다른 글
Stream 연습 문제 2 - 심화 (0) | 2022.01.12 |
---|---|
Stream 연습 문제 1 - 기본 (0) | 2022.01.12 |
string concatenation compile optimization (0) | 2022.01.05 |
ThreadLocal의 활용 - 트랜잭션 동기화 (0) | 2021.12.22 |
ThreadLocal의 정의와 사용법 (0) | 2021.12.20 |