String 의 + 연산

서승원·2023년 4월 26일
0

사건의 발단

버릇처럼 StringBuilder 를 생성해서 필요한 문자열을 만들고 있었는데, IntelliJ에서 경고를 해왔다.

왜? String 을 + 연산 하면 메모리에 낭비가 생기는 거 아니었어?

String 의 + 연산과 StringBuilder의 성능 차이에 대해 서칭하다가 알게 된 사실

    public String plusOperate() {
        String result = "first";
        result += "second";
        result += "third";
        result += "forth";
        result += "fifth";
        return result;
    }

String 의 + 연산은 bytecode에서 자동으로 StringBuilder init, append로 변경돼서 호출된다.

반전

Amazon Corretto 17 의 ByteCode 결과는 또 달랐다.

LINENUMBER 6 L1
    ALOAD 1
    INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;)Ljava/lang/String; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      // arguments:
      "\u0001second"
    ]

StringConcatFactory 의 makeConcatWithConstants 메소드를 호출한다.
예상과 다른 결과에 다시 검색..

jdk 9 버젼부터 StringBuilder 혹은 StringBuffer 를 이용한 String 의 연산을 사용하지 않는다.
그이유는
1) StringBuilder 혹은 StringBuffer 는 별도로 할당하지 않으면 16 characters 의 크기를 할당받아 메모리 재할당으로 인한 추가 비용이 발생하기 쉬웠고,
2) loop 안에서의 호출 시 반복되는 객체의 생성이 발생한다.

확인해보니 정말 temurin 1.8 바이트코드는 결과는

    LINENUMBER 8 L1
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "second"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 1

그렇다면 loop에서는 ?

for loop

Corretto 17

  • source
    public String forLoopPlus() {
        String result = "";

        for(int index = 0; index < 100; ++index) {
            result = result + index;
        }

        return result;
    }
    
  • bytecode
 L2
   FRAME APPEND [java/lang/String I]
    ILOAD 2
    BIPUSH 100
    IF_ICMPGE L3
   L4
    LINENUMBER 24 L4
    ALOAD 1
    ILOAD 2
    INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;I)Ljava/lang/String; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      // arguments:
      "\u0001\u0001"
    ]
    ASTORE 1
   L5
    LINENUMBER 23 L5
    IINC 2 1
    GOTO L2
  • makeConcatWithConstants 이 loop 안에서 반복되는 모습, 생각했던 결과

temurin 1.8

  • bytecode
 L2
   FRAME APPEND [java/lang/String I]
    ILOAD 2
    BIPUSH 100
    IF_ICMPGE L3
   L4
    LINENUMBER 18 L4
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ILOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 1
   L5
    LINENUMBER 17 L5
    IINC 2 1
    GOTO L2

정말 계속해서 StringBuilder 가 init된다..

while loop

for 와 같은 결과.

요약

  • String의 + 연산에 성능적 문제는 거의 없을 것 같다. 다만, 얼마나 readable 하게 짤 수 있는지?
  • loop 에서도 문제되지 않는다. 쓸일은 거의 없을 것 같지만 ..?
  • String 의 + 연산은 버전을 거듭하며 변경돼왔다. String 의 생성 후 메모리 재할당, StringBuilder(Buffer) 객체 활용, StringConcatFactory 의 활용

reference

https://jerry92k.tistory.com/50
https://dzone.com/articles/string-concatenation-performacne-improvement-in-ja
https://choichumji.tistory.com/135

profile
2년차 백엔드 개발자, crimy

0개의 댓글