Java 9에서 문자열 연결은 어떻게 구현됩니까?


111

JEP 280에 쓰여진대로 : Indify String Concatenation :

JDK 라이브러리 함수에 대한 호출을 사용하기 위해에서 String생성 된 정적 연결 바이트 코드 시퀀스를 변경합니다 . 이렇게하면에 의해 방출 된 바이트 코드를 추가로 변경하지 않고도 향후 연결 최적화가 가능해집니다 .javacinvokedynamicStringjavac

여기에서 invokedynamic호출 의 사용이 무엇 이며 바이트 코드 연결이 어떻게 다른지 이해하고 싶습니다 invokedynamic.


11
나는 그것에 대해 얼마 전에 썼습니다. 도움이된다면 답으로 요약하겠습니다.
Nicolai

10
또한 새로운 문자열 연결 메커니즘의 요점을 잘 설명하는이 비디오를보십시오. youtu.be/wIyeOaitmWM?t=37m58s
ZhekaKozlov

3
@ZhekaKozlov 나는 귀하의 의견을 두 번 찬성 할 수 있기를 바랍니다. 실제로이 모든 것을 구현하는 사람들로부터 오는 링크가 최고입니다.
Eugene

2
@Nicolai : 그것은 좋을 것이고, 여기에있는 다른 어떤 것보다 더 나은 대답이 될 것입니다. 당신이 할 때 통합하고 싶은 내 대답의 모든 부분은 자유롭게 느끼십시오. 넓은 대답의 일부로 (기본적으로) 전체를 포함하면 내 대답을 삭제하겠습니다. 또는 내 답변이 눈에 잘 띄기 때문에 추가하고 싶다면 커뮤니티 위키로 만들었습니다.
TJ Crowder

답변:


95

"오래된"방식은 StringBuilder일련 의 지향적 인 작업을 출력 합니다. 이 프로그램을 고려하십시오.

public class Example {
    public static void main(String[] args)
    {
        String result = args[0] + "-" + args[1] + "-" + args[2];
        System.out.println(result);
    }
}

JDK 8 또는 이전 버전으로 컴파일 한 다음 사용 javap -c Example하여 바이트 코드를 보면 다음과 같은 내용이 표시됩니다.

공용 클래스 예 {
  공개 예제 ();
    암호:
       0 : aload_0
       1 : invokespecial # 1 // 메소드 java / lang / Object. "<init>":() V
       4 : 반환

  public static void main (java.lang.String []);
    암호:
       0 : 새로운 # 2 // 클래스 java / lang / StringBuilder
       3 : 복제
       4 : invokespecial # 3 // 메소드 java / lang / StringBuilder. "<init>":() V
       7 : aload_0
       8 : 아이콘 t_0
       9 : aaload
      10 : invokevirtual # 4 // 메소드 java / lang / StringBuilder.append : (Ljava / lang / String;) Ljava / lang / StringBuilder;
      13 : ldc # 5 // 문자열-
      15 : invokevirtual # 4 // 메소드 java / lang / StringBuilder.append : (Ljava / lang / String;) Ljava / lang / StringBuilder;
      18 : aload_0
      19 : iconst_1
      20 : aaload
      21 : invokevirtual # 4 // 메소드 java / lang / StringBuilder.append : (Ljava / lang / String;) Ljava / lang / StringBuilder;
      24 : ldc # 5 // 문자열-
      26 : invokevirtual # 4 // 메소드 java / lang / StringBuilder.append : (Ljava / lang / String;) Ljava / lang / StringBuilder;
      29 : aload_0
      30 : iconst_2
      31 : aaload
      32 : invokevirtual # 4 // 메소드 java / lang / StringBuilder.append : (Ljava / lang / String;) Ljava / lang / StringBuilder;
      35 : invokevirtual # 6 // 메소드 java / lang / StringBuilder.toString :() Ljava / lang / String;
      38 : astore_1
      39 : getstatic # 7 // 필드 java / lang / System.out : Ljava / io / PrintStream;
      42 : aload_1
      43 : invokevirtual # 8 // 메소드 java / io / PrintStream.println : (Ljava / lang / String;) V
      46 : 반환
}

당신이 볼 수 있듯이, 그것은 생성 StringBuilder및 사용을 append. 내장 버퍼의 기본 용량이 StringBuilder16 자에 불과하기 때문에 이것은 상당히 비효율적 인 것으로 유명 하며 컴파일러 가 미리 더 많은 할당을 알 수 있는 방법이 없으므로 결국 재 할당해야합니다. 또한 많은 메서드 호출입니다. (JVM은 때때로 이러한 호출 패턴을 감지하고 다시 작성하여 더 효율적으로 만들 수 있습니다.)

Java 9가 생성하는 것을 살펴 보겠습니다.

공용 클래스 예 {
  공개 예제 ();
    암호:
       0 : aload_0
       1 : invokespecial # 1 // 메소드 java / lang / Object. "<init>":() V
       4 : 반환

  public static void main (java.lang.String []);
    암호:
       0 : aload_0
       1 : 아이콘
       2 : aaload
       3 : aload_0
       4 : iconst_1
       5 : aaload
       6 : aload_0
       7 : iconst_2
       8 : aaload
       9 : invokedynamic # 2, 0 // InvokeDynamic # 0 : makeConcatWithConstants : (Ljava / lang / String; Ljava / lang / String; Ljava / lang / String;) Ljava / lang / String;
      14 : astore_1
      15 : getstatic # 3 // 필드 java / lang / System.out : Ljava / io / PrintStream;
      18 : aload_1
      19 : invokevirtual # 4 // 메소드 java / io / PrintStream.println : (Ljava / lang / String;) V
      22 : 반환
}

오,하지만 더 짧습니다. :-) makeConcatWithConstantsfrom에 대한 단일 호출을 수행 StringConcatFactory하며 Javadoc에 다음과 같이 표시됩니다.

유형 적응 및 인수의 부분 평가 후에 알려진 유형의 알려진 수의 인수를 효율적으로 연결하는 데 사용할 수있는 문자열 연결 메서드의 생성을 용이하게하는 메서드입니다. 이러한 메소드는 일반적으로 Java 프로그래밍 언어 의 문자열 연결 기능 을 지원 하기 위해 invokedynamic호출 사이트의 부트 스트랩 메소드 로 사용됩니다 .


41
이것은 거의 6 년 전에 그날 작성한 답변을 생각 나게합니다. stackoverflow.com/a/7586780/330057- 누군가가 StringBuilder를 만들어야하는지 아니면 +=for 루프에서 일반 오래된 것을 사용해야하는지 물었습니다 . 나는 그들에게 그것이 상황에 따라 다르다고 말했지만, 그들이 언젠가 길을 따라 연결하는 더 나은 방법을 찾을 수 있다는 것을 잊지 말자. 핵심 줄은 실제로 두 번째 줄입니다.So by being smart, you have caused a performance hit when Java got smarter than you.
corsiKa

3
@corsiKa : LOL! 하지만 와우, 거기에 도착하는 데 오랜 시간이 걸렸습니다 (6 년이 아니라 22 정도라는 뜻입니다 ... :-))
TJ Crowder

1
@supercat : 내가 이해했듯이 성능이 중요한 경로의 메서드에 전달할 varargs 배열을 만드는 것이 이상적이지 않다는 점에서 두 가지 이유가 있습니다. 또한를 사용 invokedynamic하면 각 호출에 대한 메서드 호출 및 디스패치 테이블의 오버 헤드없이 런타임에 서로 다른 연결 전략을 선택하고 첫 번째 호출에 바인딩 할 수 있습니다. 여기JEP있는 nicolai의 기사 에서 더 많은 것 .
TJ Crowder

1
@supercat : 그리고 최종 결과로 변환되지 않고 문자열로 미리 변환되어야하므로 비 Strings와 잘 작동하지 않을 것이라는 사실이 있습니다. 더 비효율적입니다. 그것을 만들 수 Object있지만, 당신은 모든 프리미티브 ... 상자해야 할 것 (BTW, 그의 뛰어난 문서에서 어떤 니콜라이 커버를.)
TJ 크라우

2
@supercat String.concat(String)구현 결과 문자열의 배열을 제자리에 생성하는 이미 존재하는 메서드를 참조했습니다 . 우리가 toString()임의의 개체 를 호출해야 할 때 이점이 문제가됩니다 . 마찬가지로 배열을받는 메서드를 호출 할 때 호출자는 배열을 만들고 채워야하므로 전체적인 이점이 줄어 듭니다. 그러나 이제는 새로운 솔루션이 기본적으로 고려하고 있던 것이기 때문에 관련성이 없습니다. 단, 복싱 오버 헤드가없고, 배열 생성이 필요하지 않으며, 백엔드가 특정 시나리오에 대해 최적화 된 핸들러를 생성 할 수 있다는 점이 다릅니다.
Holger

20

invokedynamic문자열 연결 최적화에 사용되는 구현 에 대한 세부 사항을 살펴보기 전에 invokedynamic이란 무엇이며 어떻게 사용합니까?

invokedynamic 명령어 는 JVM에서 동적 언어를위한 컴파일러 및 런타임 시스템의 구현을 단순화하고 잠재적으로 개선합니다 . 이는 언어 구현자가 invokedynamic다음 단계를 포함하는 명령어로 사용자 정의 연결 동작을 정의 할 수 있도록하여 수행합니다.


문자열 연결 최적화 구현을 위해 가져온 변경 사항을 통해 이러한 내용을 안내해 드릴 것입니다.

  • 부트 스트랩 방법을 정의 : - Java9에 대한 부트 스트랩 방법으로 invokedynamic호출 사이트 것은 주로 문자열 연결을 지원 makeConcat하고 makeConcatWithConstants도입했다 StringConcatFactory구현입니다.

    invokedynamic의 사용은 런타임까지 번역 전략을 선택하는 대안을 제공합니다. 에서 사용 된 번역 전략 은 이전 자바 버전에서 소개 된 StringConcatFactory것과 유사합니다 LambdaMetafactory. 또한 질문에 언급 된 JEP의 목표 중 하나는 이러한 전략을 더욱 확장하는 것입니다.

  • 상수 풀 항목 지정 :- invokedynamic명령어 MethodHandles.Lookup컨텍스트에서 메서드 핸들을 생성하는 팩토리 인 (1) 객체 invokedynamic, (2) String동적 호출에서 언급 된 메서드 이름 인 객체 이외 의 명령어에 대한 추가 정적 인수 입니다. 사이트 및 (3) MethodType개체, 동적 호출 사이트의 확인 된 형식 서명.

    코드를 연결하는 동안 이미 연결되어 있습니다. 런타임에 부트 스트랩 메서드는 연결을 수행하는 실제 코드에서 실행되고 연결됩니다. invokedynamic적절한 invokestatic호출로 호출을 다시 작성합니다 . 이렇게하면 상수 풀에서 상수 문자열이로드되고 부트 스트랩 메서드 정적 인수는 이러한 상수와 기타 상수를 부트 스트랩 메서드 호출에 직접 전달하는 데 활용됩니다.

  • invokedynamic 명령어 사용 :-초기 호출 중에 호출 대상을 한 번 부트 스트랩하는 수단을 제공하여 지연 연결 기능을 제공합니다. 여기서 최적화에 대한 구체적인 아이디어는 전체 StringBuilder.append댄스 를 연결이 필요한 값을 허용 하는를 invokedynamic호출 하는 간단한 호출로 대체 java.lang.invoke.StringConcatFactory하는 것입니다.

Indify 문자열 병합 예 제안서 공유 상태와 동일한 방법으로 Java9 애플리케이션의 벤치마킹 @TJ 크라우는 컴파일되고, 바이트 코드의 차이는 VARYING 구현 사이 상당히 보인다.


17

여기에 약간의 세부 사항을 추가하겠습니다. 얻을 주요 부분은 문자열 연결이 수행되는 방식이 더 이상 컴파일 시간 이 아니라 런타임 결정이라는 것 입니다. 따라서 변경 될 수 있습니다. 즉, Java-9에 대해 코드를 한 번 컴파일했으며 재 컴파일 할 필요없이 원하는대로 기본 구현을 변경할 수 있습니다.

두 번째 요점은 현재 다음과 6 possible strategies for concatenation of String같습니다.

 private enum Strategy {
    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder}.
     */
    BC_SB,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but trying to estimate the required storage.
     */
    BC_SB_SIZED,

    /**
     * Bytecode generator, calling into {@link java.lang.StringBuilder};
     * but computing the required storage exactly.
     */
    BC_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also tries to estimate the required storage.
     */
    MH_SB_SIZED,

    /**
     * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}.
     * This strategy also estimate the required storage exactly.
     */
    MH_SB_SIZED_EXACT,

    /**
     * MethodHandle-based generator, that constructs its own byte[] array from
     * the arguments. It computes the required storage exactly.
     */
    MH_INLINE_SIZED_EXACT
}

매개 변수를 통해 이들 중 하나를 선택할 수 있습니다 -Djava.lang.invoke.stringConcat. 공지 StringBuilder여전히 옵션입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.