자바 & 스프링

string관련 클래스 성능 비교

p829911 2022. 1. 6. 19:14

2021.12.08 - [자바 & 스프링] - java - String, StringBuilder, StringBuffer

 

java - String, StringBuilder, StringBuffer

String, StringBuilder, StringBuffer는 자바에서 문자열을 다루는 대표적인 클래스이다. String Java에서 String 객체를 생성하는 방법은 2가지가 있는데 하나는 "" 큰 따옴표를 사용하는 것이고, 두번째는 new..

p829911.tistory.com

이전에 문자열을 다루는 자바 클래스 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