2016-10-26 2 views
7

나는 최근 자바 버전 문자열 연결에Java는 +와 문자열 연결을 최적화하는 데 어느 정도 걸리는가?

String test = one + "two"+ three; 

StringBuilder를 사용하도록 최적화받을 것이라는 점을 알고있다.

그러나이 행에 도달 할 때마다 새로운 StringBuilder이 생성되거나 모든 문자열 연결에 사용되는 단일 스레드 로컬 StringBuilder가 생성됩니까?

즉, 자주 사용하는 스레드 로컬 StringBuilder를 만들어 자주 호출하는 메서드의 성능을 향상시켜 재사용 할 수 있습니까? 그렇지 않으면 큰 이익이 없을까요?

필자는이 문제에 대한 테스트를 작성할 수 있지만 컴파일러/JVM에만 해당되는지 또는 더 일반적으로 대답 할 수 있는지 궁금합니다.

+1

표현식을 연결할 때 재진입을 조심하십시오. – SLaks

+1

마지막으로, 꽤 바보 같아서'StringBuilder'가 반복해서 재 할당되도록했습니다. 그러나 이것은 오라클의 JDK에만 해당되며 결과로 생성되는 바이트 코드를 살펴 보았으므로 JVM이 수행 할 수있는 최적화를 고려하지 않았습니다. 내 규칙은 다음과 같습니다. 물론 99.999 %는 신경 쓰지 않아도됩니다. .001 %를 돌보는 곳에서는 총 결과를 처리 할만큼 충분히 큰 할당 된 명시 적'StringBuilder'를 사용하십시오. –

+0

한 줄보다 더 많은 문자열 조작을하지 않는 한, T.J .: 99.999 %의 시간에는 아무런 차이가 없을 것입니다. JVM은 실제로 모든 메모리를 로컬 스레드로 할당합니다 (다른 스레드와 공유해야 할 때까지). 따라서 스레드 로컬은 아무런 효과가 없을 것입니다. – markspace

답변

6

내가 아는 한, StringBuilder 인스턴스를 재사용하는 코드를 생성하는 컴파일러가 없습니다. 특히 javac이고 ECJ는 재사용 코드를 생성하지 않습니다.

이러한 재사용을하지 않는 것이 중요하다는 점을 강조하는 것이 중요합니다. ThreadLocal 변수에서 인스턴스를 검색하는 코드가 TLAB의 일반 할당보다 빠르다고 가정하는 것은 안전하지 않습니다. 해당 인스턴스를 회수하기위한 로컬 GC주기의 잠재적 비용을 추가하려고해도 비용에 대한 비율을 확인할 수있는 한 결론을 내릴 수 없습니다.

따라서 빌더를 재사용하려는 코드는 메모리가 낭비되고 복잡해집니다. 빌더가 성능 재사용의 이점없이 실제로 재사용 될지 여부를 알지 못하고 빌더가 계속 작동하기 때문에 메모리가 낭비됩니다.

는 특히 우리는 또한 핫스팟과 같은

  • 의 JVM 위의 문을 모두이 같은 순수한 지역 할당을 생략하다 할 수있는 분석 탈출이 또한 배열의 복사 비용을 생략하다 작업의 크기를 조정할 수 있음을 고려할 때
  • 컴파일 된 코드는 일반적인 패턴을 따르는 경우 이러한 정교한의 JVM은 일반적으로도 최고의 작품 StringBuilder 기반 연결 전용선 최적화를 가지고

Java 9에서는 그림이 다시 변경됩니다. 그런 다음 런타임에 JRE 제공 팩토리에 링크되는 invokedynamic 명령으로 문자열 연결이 컴파일됩니다 (StringConcatFactory 참조). 그런 다음 JRE는 특정 JVM에 이점이있는 경우 버퍼 재사용을 포함하여 코드를 어떻게 보이게할지 결정합니다. 또한 코드 순서가 할당 순서와 StringBuilder으로의 다중 호출보다는 단일 명령 만 필요하므로 코드 크기가 줄어 듭니다.

+1

with jdk-9 그림이 변경됩니다. * 극적으로 다시 : – Eugene

6

jdk-9 String Concatenation에 얼마나 많은 노력을 기울 였는지 놀라실 것입니다. 첫 번째 javac은 StringBuilder#append에 대한 호출 대신 invokedynamic을 내 보냅니다. invokedynamic은 CallSite을 반환하고 MethodHandle (실제로는 일련의 MethodHandles)을 포함합니다.

따라서 문자열 결합에 대해 실제로 수행 된 결정이 런타임으로 이동됩니다. 단점은 문자열의 연결을 처음으로 느리게하는 것입니다 (같은 유형의 인수에 대해).

이 그럼 당신은 문자열을 연결 때 선택할 수있는 전략의 시리즈가 있습니다 (디폴트에게 하나의 매개 변수 java.lang.invoke.stringConcat를 통해를 대체 할 수 있습니다) :

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 
} 

기본 전략은 다음과 같습니다 MH_INLINE_SIZED_EXACT짐승이다!

그것은 문자열을 구축하기 위해 패키지 개인 생성자를 사용 (가장 빠른 인) :

/* 
* Package private constructor which shares value array for speed. 
*/ 
String(byte[] value, byte coder) { 
    this.value = value; 
    this.coder = coder; 
} 

먼저 그렇게 필터라는 만듭니다이 전략; 이들은 기본적으로 들어오는 매개 변수를 String 값으로 변환하는 메서드 핸들입니다. 하나 예상 할 수 있듯이,이 MethodHandles 대부분의 경우는 MethodHandle을 생산에 호출하는 것을 Stringifiers라는 클래스에 저장됩니다

String.valueOf(YourInstance) 

을 그래서 당신은 당신이 연결할 할 3 개체가있는 경우 위임합니다 3 MethodHandles가있을 것입니다 String.valueOf(YourObject)으로 변경하면 효과적으로 개체를 문자열로 변형 한 것입니다. 이 수업에는 여전히 이해할 수없는 개조가 있습니다. 필요에 따라 StringifierMost (문자열 만 참조로 변환, 부동 및 복식으로 변환) 및 StringifierAny 클래스가 별도로 있어야합니다.

MH_INLINE_SIZED_EXACT은 바이트 배열이 정확한 크기로 계산된다고 말합니다. 그것을 계산할 방법이 있습니다.

이 방법은 StringConcatHelper#mixLen의 입력 매개 변수 (참조/부동/이중)의 문자열 버전을 사용하는 방법을 통해 수행됩니다. 이 시점에서 최종 문자열의 크기를 알 수 있습니다. 글쎄, 우리는 실제로 그것을 모른다., 우리는 그것을 계산할 MethodHandle이있다.

여기서 언급 할만한 가치가있는 String jdk-9의 또 다른 변경 사항이 있습니다. coder 필드가 추가되었습니다. 이것은, String의 사이즈/동일성/charAt를 계산하기 위해서 필요합니다. 크기에 필요하기 때문에이를 계산해야합니다. 이것은 StringConcatHelper#mixCoder을 통해 이루어집니다.

UR 배열을 생성하는 MethodHandle 위임하는이 시점에서 안전 : 가

@ForceInline 
    private static byte[] newArray(int length, byte coder) { 
     return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder); 
    } 
어떻게 각 요소

을 추가됩니다 를? 방법을 통해 StringConcatHelper#prepend.

이제는 바이트를 사용하는 String의 생성자를 호출하는 데 필요한 모든 세부 사항 만 필요합니다.


이러한 모든 작업 (및 많은 다른 내가 편의상 생략 한)이 첨부 된이 실제로 발생했을 때 호출 될 MethodHandle 발광 통해 처리됩니다.

+1

간단한 조작의 세부 사항이 IMO를 매료시키는 간단한 이유 때문에이 대답에서 조금 벗어났습니다. – Eugene

+0

불행히도 그것은 정말로 직접 질문에 대답하지는 않지만 정말로 흥미 롭습니다 - 그래서 나는 진드기를 넘겨 줄 수 없다고 느낍니다. ( –

+1

@TimB 전적으로 동의합니다, 이것은 틱에 관한 것이 아닙니다 :). 받아 들여진 대답이 올바른 것입니다. – Eugene