추가 사례에서 Java가 연속 정수를 켜는 이유는 무엇입니까?


276

내 주요 프로그램 논리의 여러 지점에서 호출되는 핫 함수에서 실행되므로 고도로 최적화 해야하는 Java 코드를 작업 중입니다. 이 코드의 일부에는 double변수에 10임의의 음이 아닌 값을 곱한 값 이 포함됩니다 int exponent. 곱한 값을 얻는 한 가지 빠른 방법 (편집 : 가장 빠른 것은 아니지만 아래 업데이트 2 참조) switchexponent다음 과 같습니다.

double multiplyByPowerOfTen(final double d, final int exponent) {
   switch (exponent) {
      case 0:
         return d;
      case 1:
         return d*10;
      case 2:
         return d*100;
      // ... same pattern
      case 9:
         return d*1000000000;
      case 10:
         return d*10000000000L;
      // ... same pattern with long literals
      case 18:
         return d*1000000000000000000L;
      default:
         throw new ParseException("Unhandled power of ten " + power, 0);
   }
}

위의 주석 처리 된 타원은 case int상수가 1 씩 증가 함을 나타내 므로 case위의 코드 스 니펫 에는 실제로 19 초가 있습니다. 나는 확실히 실제로 10의 모든 힘을 필요 여부 아니었다 때문에 case10을 통해 18, 나는이 완료 천만 작업에 시간을 비교하는 일부 microbenchmarks 실행 switch대 문 switch에서만 case이야 0통해 9과 ( exponent이하 9로 제한을 pared-down을 끊지 마십시오 switch). 나는 switch더 많은 case진술 로 더 길어질수록 실제로 더 빨리 실행 된다는 다소 놀라운 결과를 얻었 습니다.

case잠깐 동안 , 나는 더미 값을 반환하는 더 많은 s를 추가하려고 시도 했으며 약 22-27로 선언 된 스위치로 스위치를 더 빠르게 실행할 수 있음을 발견했습니다 case(코드가 실행되는 동안 더미 케이스가 실제로 맞지 않더라도) ). (다시 말해서, cases는 선행 case상수 를 씩 증가시켜 연속적인 방식으로 추가 되었습니다 1.) 이러한 실행 시간 차이는 그다지 중요하지 않습니다. 와 exponent사이 의 임의의 경우 더미 패딩 된 명령문은 1.49 초에서 1 천만 회의 실행과 패딩되지 않은 경우의 1.54 초의 1000 만회의 실행을 완료합니다. 버전, 실행 당 총 5ns의 총 절감. 따라서 패딩에 집착하는 것은 아닙니다.010switchswitch최적화 관점에서 노력할 가치가있는 진술. 그러나 나는 여전히 더 많은 것들이 추가 될 때 실행 switch속도가 느려지지 않거나 (또는 ​​아마도 일정한 O (1) 시간을 유지하지 않는) 호기심과 반 직관적입니다 case.

벤치마킹 결과 전환

이것은 무작위로 생성 된 exponent값 에 대한 다양한 제한으로 실행 한 결과 입니다. 나는 결과에 이르기까지 모든 방법을 포함하지 않았다 1에 대한 exponent제한을하지만, 곡선의 일반적인 형태는 12-17 경우 마크 주변의 능선, 그리고 18 ~ 28 인치 사이의 계곡으로, 동일하게 유지됩니다. 모든 테스트는 동일한 테스트 입력을 보장하기 위해 임의의 값에 대해 공유 컨테이너를 사용하여 JUnitBenchmarks에서 실행되었습니다. 또한 switch주문 관련 테스트 문제의 가능성을 시도하고 제거 하기 위해 가장 긴 문장부터 가장 짧은 것 까지 순서대로 테스트를 실행했습니다 . 누군가 가이 결과를 재현하려고한다면 github 저장소에 테스트 코드를 넣었습니다.

무슨 일이야? 내 아키텍처 또는 마이크로 벤치 마크 구성의 일부 미묘한 점이 있습니까? 또는 자바는 switch에서 실행하기 위해 조금 더 빨리 정말 1828 case그에서보다 범위 11까지 17?

github 테스트 저장소 "스위치 실험"

업데이트 : 벤치마킹 라이브러리를 상당히 정리하고 / results에 텍스트 파일을 추가하여 더 넓은 범위의 가능한 exponent값을 출력했습니다 . 또한 테스트 코드에 Exceptionfrom 을 던지지 않는 옵션을 추가 default했지만 결과에 영향을 미치지 않는 것으로 보입니다.

업데이트 2 : 2009 년 xkcd 포럼 ( http://forums.xkcd.com/viewtopic.php?f=11&t=33524) 에서이 문제에 대한 좋은 토론을 발견했습니다 . OP의 사용에 대한 논의는 Array.binarySearch()위의 지수 패턴의 간단한 배열 기반 구현에 대한 아이디어를 제공했습니다. 항목이 무엇인지 알고 있기 때문에 이진 검색이 필요하지 않습니다 array. 사용 switch하는 제어 흐름 중 일부를 희생시키면서을 사용하는 것보다 약 3 배 빠르게 실행되는 것으로 보입니다 switch. 이 코드는 github 저장소에 추가되었습니다.


64
이제 모든 Google 직원은 switch분명히 가장 최적의 솔루션이므로 모든 진술 에서 정확히 22 건의 사례를 갖게됩니다 . : D (내지도에 이것을 표시하지 마십시오.)
asteri

2
더 간단한 SSCCE가 있습니까? 이것은 나를 위해 컴파일되지 않습니다. Java 성능에 약한 한, 이것에 대해보고 싶습니다.
Mysticial

5
문자열 기반 사례에 대한 답변에서 "JVM의 스위치" 섹션이 도움이 될 수 있습니다. 여기에서 일어나는 일은 귀하가에서 (으)로 전환하고 있다고 생각 lookupswitch합니다 tableswitch. 코드를 분해 javap하면 확실하게 표시됩니다.
erickson

2
리포지토리의 / lib 폴더에 종속성 jar을 추가했습니다. @Mysticial 죄송합니다, 나는 이미이 토끼 구멍을 내려가는 데 너무 많은 시간을 보냈습니다! 테스트 클래스에서 "Extendeds AbstractBenchmark"를 제거하고 "com.carrotsearch"가져 오기를 제거하면 JUnit 종속성만으로 실행할 수 있지만 carrotsearch 항목은 JIT에서 발생하는 일부 노이즈를 필터링하는 데 매우 좋습니다. 워밍업 기간. 불행히도 IntelliJ 외부에서 이러한 JUnit 테스트를 실행하는 방법을 모르겠습니다.
Andrew Bissell

2
@AndrewBissell 나는 훨씬 더 간단한 벤치 마크로 결과를 재현했습니다. 중소형 성능에 대한 브랜치 대 테이블은 다소 분명한 추측이었습니다. 그러나 30 건의 사례에 대한 딥에 대한 다른 사람보다 더 나은 통찰력은 없습니다 ...
Mysticial

답변:


228

다른 답변 에서 지적한 것처럼 사례 값이 인접하지 않기 때문에 (희소 한 것이 아니라) 다양한 테스트에 대해 생성 된 바이트 코드는 스위치 테이블 (바이트 코드 명령 tableswitch)을 사용합니다.

그러나 JIT가 작업을 시작하고 바이트 코드를 어셈블리로 컴파일하면 tableswitch명령에 항상 포인터 배열이 생성되지는 않습니다. 스위치 테이블이 lookupswitch( if/ else if구조 와 유사하게 ) 변환되는 경우도 있습니다.

JIT (hotspot JDK 1.7)에서 생성 된 어셈블리를 디 컴파일하면 17 개 이하의 경우에 if / else, 18 개 이상의 경우 (더 효율적인) 포인터 배열이 연속적으로 사용됩니다.

이 매직 넘버 18을 사용하는 이유는 MinJumpTableSizeJVM 플래그 의 기본값 (코드의 352 행) 으로 내려온 것 같습니다 .

핫스팟 컴파일러 목록에서 문제를 제기했으며 과거 테스트의 유산으로 보입니다 . 이 기본값 더 많은 벤치마킹이 수행 된JDK 8에서 제거되었습니다 .

마지막으로 메소드가 너무 길어지면 (테스트에서 25 건 이상) 기본 JVM 설정으로 더 이상 인라인되지 않습니다.이 시점에서 성능이 저하 될 가능성이 가장 큽니다.


5 가지 경우의 디 컴파일 된 코드는 다음과 같습니다 (cmp / je / jg / jmp 명령어, if / goto의 어셈블리에 주목).

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x00000000024f0160: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x00000000024f0167: push   rbp
  0x00000000024f0168: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x00000000024f016c: cmp    edx,0x3
  0x00000000024f016f: je     0x00000000024f01c3
  0x00000000024f0171: cmp    edx,0x3
  0x00000000024f0174: jg     0x00000000024f01a5
  0x00000000024f0176: cmp    edx,0x1
  0x00000000024f0179: je     0x00000000024f019b
  0x00000000024f017b: cmp    edx,0x1
  0x00000000024f017e: jg     0x00000000024f0191
  0x00000000024f0180: test   edx,edx
  0x00000000024f0182: je     0x00000000024f01cb
  0x00000000024f0184: mov    ebp,edx
  0x00000000024f0186: mov    edx,0x17
  0x00000000024f018b: call   0x00000000024c90a0  ; OopMap{off=48}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
                                                ;   {runtime_call}
  0x00000000024f0190: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
  0x00000000024f0191: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffffa7]        # 0x00000000024f0140
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@52 (line 62)
                                                ;   {section_word}
  0x00000000024f0199: jmp    0x00000000024f01cb
  0x00000000024f019b: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff8d]        # 0x00000000024f0130
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@46 (line 60)
                                                ;   {section_word}
  0x00000000024f01a3: jmp    0x00000000024f01cb
  0x00000000024f01a5: cmp    edx,0x5
  0x00000000024f01a8: je     0x00000000024f01b9
  0x00000000024f01aa: cmp    edx,0x5
  0x00000000024f01ad: jg     0x00000000024f0184  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x00000000024f01af: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff81]        # 0x00000000024f0138
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@64 (line 66)
                                                ;   {section_word}
  0x00000000024f01b7: jmp    0x00000000024f01cb
  0x00000000024f01b9: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff67]        # 0x00000000024f0128
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@70 (line 68)
                                                ;   {section_word}
  0x00000000024f01c1: jmp    0x00000000024f01cb
  0x00000000024f01c3: mulsd  xmm0,QWORD PTR [rip+0xffffffffffffff55]        # 0x00000000024f0120
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
                                                ;   {section_word}
  0x00000000024f01cb: add    rsp,0x10
  0x00000000024f01cf: pop    rbp
  0x00000000024f01d0: test   DWORD PTR [rip+0xfffffffffdf3fe2a],eax        # 0x0000000000430000
                                                ;   {poll_return}
  0x00000000024f01d6: ret    

18 건의 경우 어셈블리는 다음과 같이 보입니다 (사용되는 포인터 배열에 주목하고 모든 비교가 필요하지 jmp QWORD PTR [r8+r10*1]않음 : 올바른 곱셈으로 직접 점프)-성능 향상의 이유는 다음과 같습니다.

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x000000000287fe20: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x000000000287fe27: push   rbp
  0x000000000287fe28: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x000000000287fe2c: cmp    edx,0x13
  0x000000000287fe2f: jae    0x000000000287fe46
  0x000000000287fe31: movsxd r10,edx
  0x000000000287fe34: shl    r10,0x3
  0x000000000287fe38: movabs r8,0x287fd70       ;   {section_word}
  0x000000000287fe42: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x000000000287fe46: mov    ebp,edx
  0x000000000287fe48: mov    edx,0x31
  0x000000000287fe4d: xchg   ax,ax
  0x000000000287fe4f: call   0x00000000028590a0  ; OopMap{off=52}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
                                                ;   {runtime_call}
  0x000000000287fe54: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
  0x000000000287fe55: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe8b]        # 0x000000000287fce8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@194 (line 92)
                                                ;   {section_word}
  0x000000000287fe5d: jmp    0x000000000287ff16
  0x000000000287fe62: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe86]        # 0x000000000287fcf0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@188 (line 90)
                                                ;   {section_word}
  0x000000000287fe6a: jmp    0x000000000287ff16
  0x000000000287fe6f: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe81]        # 0x000000000287fcf8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@182 (line 88)
                                                ;   {section_word}
  0x000000000287fe77: jmp    0x000000000287ff16
  0x000000000287fe7c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe7c]        # 0x000000000287fd00
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@176 (line 86)
                                                ;   {section_word}
  0x000000000287fe84: jmp    0x000000000287ff16
  0x000000000287fe89: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe77]        # 0x000000000287fd08
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@170 (line 84)
                                                ;   {section_word}
  0x000000000287fe91: jmp    0x000000000287ff16
  0x000000000287fe96: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe72]        # 0x000000000287fd10
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@164 (line 82)
                                                ;   {section_word}
  0x000000000287fe9e: jmp    0x000000000287ff16
  0x000000000287fea0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe70]        # 0x000000000287fd18
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@158 (line 80)
                                                ;   {section_word}
  0x000000000287fea8: jmp    0x000000000287ff16
  0x000000000287feaa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6e]        # 0x000000000287fd20
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@152 (line 78)
                                                ;   {section_word}
  0x000000000287feb2: jmp    0x000000000287ff16
  0x000000000287feb4: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe24]        # 0x000000000287fce0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@146 (line 76)
                                                ;   {section_word}
  0x000000000287febc: jmp    0x000000000287ff16
  0x000000000287febe: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe6a]        # 0x000000000287fd30
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@140 (line 74)
                                                ;   {section_word}
  0x000000000287fec6: jmp    0x000000000287ff16
  0x000000000287fec8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe68]        # 0x000000000287fd38
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@134 (line 72)
                                                ;   {section_word}
  0x000000000287fed0: jmp    0x000000000287ff16
  0x000000000287fed2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe66]        # 0x000000000287fd40
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@128 (line 70)
                                                ;   {section_word}
  0x000000000287feda: jmp    0x000000000287ff16
  0x000000000287fedc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe64]        # 0x000000000287fd48
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@122 (line 68)
                                                ;   {section_word}
  0x000000000287fee4: jmp    0x000000000287ff16
  0x000000000287fee6: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe62]        # 0x000000000287fd50
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@116 (line 66)
                                                ;   {section_word}
  0x000000000287feee: jmp    0x000000000287ff16
  0x000000000287fef0: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe60]        # 0x000000000287fd58
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@110 (line 64)
                                                ;   {section_word}
  0x000000000287fef8: jmp    0x000000000287ff16
  0x000000000287fefa: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5e]        # 0x000000000287fd60
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@104 (line 62)
                                                ;   {section_word}
  0x000000000287ff02: jmp    0x000000000287ff16
  0x000000000287ff04: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe5c]        # 0x000000000287fd68
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@98 (line 60)
                                                ;   {section_word}
  0x000000000287ff0c: jmp    0x000000000287ff16
  0x000000000287ff0e: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe12]        # 0x000000000287fd28
                                                ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
                                                ;   {section_word}
  0x000000000287ff16: add    rsp,0x10
  0x000000000287ff1a: pop    rbp
  0x000000000287ff1b: test   DWORD PTR [rip+0xfffffffffd9b00df],eax        # 0x0000000000230000
                                                ;   {poll_return}
  0x000000000287ff21: ret    

마지막 으로 @cHao가 발견 한 것처럼movapd xmm0,xmm1 코드 중간에 나타나는 추가 항목 을 제외하고 30 건 (아래)의 어셈블리는 18 건과 유사 하지만 성능 저하의 가장 큰 이유는 방법이 너무 큽니다. 기본 JVM 설정으로 인라인되기를 바랍니다.

[Verified Entry Point]
  # {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
  # parm0:    xmm0:xmm0   = double
  # parm1:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x0000000002524560: mov    DWORD PTR [rsp-0x6000],eax
                                                ;   {no_reloc}
  0x0000000002524567: push   rbp
  0x0000000002524568: sub    rsp,0x10           ;*synchronization entry
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
  0x000000000252456c: movapd xmm1,xmm0
  0x0000000002524570: cmp    edx,0x1f
  0x0000000002524573: jae    0x0000000002524592  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x0000000002524575: movsxd r10,edx
  0x0000000002524578: shl    r10,0x3
  0x000000000252457c: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe3c]        # 0x00000000025243c0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@364 (line 118)
                                                ;   {section_word}
  0x0000000002524584: movabs r8,0x2524450       ;   {section_word}
  0x000000000252458e: jmp    QWORD PTR [r8+r10*1]  ;*tableswitch
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
  0x0000000002524592: mov    ebp,edx
  0x0000000002524594: mov    edx,0x31
  0x0000000002524599: xchg   ax,ax
  0x000000000252459b: call   0x00000000024f90a0  ; OopMap{off=64}
                                                ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
                                                ;   {runtime_call}
  0x00000000025245a0: int3                      ;*new  ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
  0x00000000025245a1: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe27]        # 0x00000000025243d0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@358 (line 116)
                                                ;   {section_word}
  0x00000000025245a9: jmp    0x0000000002524744
  0x00000000025245ae: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe22]        # 0x00000000025243d8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@348 (line 114)
                                                ;   {section_word}
  0x00000000025245b6: jmp    0x0000000002524744
  0x00000000025245bb: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe1d]        # 0x00000000025243e0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@338 (line 112)
                                                ;   {section_word}
  0x00000000025245c3: jmp    0x0000000002524744
  0x00000000025245c8: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe18]        # 0x00000000025243e8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@328 (line 110)
                                                ;   {section_word}
  0x00000000025245d0: jmp    0x0000000002524744
  0x00000000025245d5: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe13]        # 0x00000000025243f0
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@318 (line 108)
                                                ;   {section_word}
  0x00000000025245dd: jmp    0x0000000002524744
  0x00000000025245e2: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0e]        # 0x00000000025243f8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@308 (line 106)
                                                ;   {section_word}
  0x00000000025245ea: jmp    0x0000000002524744
  0x00000000025245ef: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe09]        # 0x0000000002524400
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@298 (line 104)
                                                ;   {section_word}
  0x00000000025245f7: jmp    0x0000000002524744
  0x00000000025245fc: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe04]        # 0x0000000002524408
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@288 (line 102)
                                                ;   {section_word}
  0x0000000002524604: jmp    0x0000000002524744
  0x0000000002524609: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdff]        # 0x0000000002524410
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@278 (line 100)
                                                ;   {section_word}
  0x0000000002524611: jmp    0x0000000002524744
  0x0000000002524616: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdfa]        # 0x0000000002524418
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@268 (line 98)
                                                ;   {section_word}
  0x000000000252461e: jmp    0x0000000002524744
  0x0000000002524623: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffd9d]        # 0x00000000025243c8
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@258 (line 96)
                                                ;   {section_word}
  0x000000000252462b: jmp    0x0000000002524744
  0x0000000002524630: movapd xmm0,xmm1
  0x0000000002524634: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffe0c]        # 0x0000000002524448
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@242 (line 92)
                                                ;   {section_word}
  0x000000000252463c: jmp    0x0000000002524744
  0x0000000002524641: movapd xmm0,xmm1
  0x0000000002524645: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffddb]        # 0x0000000002524428
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@236 (line 90)
                                                ;   {section_word}
  0x000000000252464d: jmp    0x0000000002524744
  0x0000000002524652: movapd xmm0,xmm1
  0x0000000002524656: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdd2]        # 0x0000000002524430
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@230 (line 88)
                                                ;   {section_word}
  0x000000000252465e: jmp    0x0000000002524744
  0x0000000002524663: movapd xmm0,xmm1
  0x0000000002524667: mulsd  xmm0,QWORD PTR [rip+0xfffffffffffffdc9]        # 0x0000000002524438
                                                ;*dmul
                                                ; - javaapplication4.Test1::multiplyByPowerOfTen@224 (line 86)
                                                ;   {section_word}

[etc.]

  0x0000000002524744: add    rsp,0x10
  0x0000000002524748: pop    rbp
  0x0000000002524749: test   DWORD PTR [rip+0xfffffffffde1b8b1],eax        # 0x0000000000340000
                                                ;   {poll_return}
  0x000000000252474f: ret    

7
@ syb0rg 솔직히 말해서 나는 훌륭한 세부 사항도 이해하지 못한다 ;-)
assylias

4
훌륭한 답변을 위해 +1! 실적이 OP 차트에서 '딥'을 종료 할 때를 비교하기 위해 30 개 이상의 사례로 무언가를 분해 할 수 있습니까?
asteri


2
@AndrewBissell 내 생각에 다른 행동은 (i) 사례 수가 18보다 클 때만 포인터 배열이 효율적임을 보여준 교차 아키텍처 성능 테스트 또는 (ii) 코드가 실행되고 프로파일 러는 런타임 중에 어떤 접근 방식이 더 나은지 결정합니다. 답을 찾을 수 없습니다.
assylias

3
30 건의 분해와 18 건의 분해는 대부분 동일하게 보입니다. 차이는 대부분 11 번째 사건 이후에 여분의 레지스터 셔플 링으로 제한되는 것으로 보입니다. JITter가 왜 그렇게하는지 말할 수 없습니다. 불필요한 것 같습니다.
cHao

46

사례 값이 좁은 범위에 배치되면 스위치-사례가 더 빠릅니다.

case 1:
case 2:
case 3:
..
..
case n:

이 경우 컴파일러는 switch 문의 모든 경우 구간에 대한 비교를 수행하지 않아도됩니다. 컴파일러는 다른 레그에서 수행 할 동작의 주소를 포함하는 점프 테이블을 만듭니다. 스위치가 수행되는 값은의 인덱스로 변환되도록 조작됩니다 jump table. 이 구현에서 switch 문에 걸리는 시간은 동등한 if-else-if 문에 걸리는 시간보다 훨씬 짧습니다. 또한 switch 문에 걸리는 시간은 switch 문에있는 케이스 레그 수와 무관합니다.

Wikipedia 에서 컴파일 섹션의 스위치 문 에 대해 설명 합니다.

입력 값의 범위가 '소규모'로 식별 할 수 있고 약간의 차이 만있는 경우 최적화 프로그램을 통합하는 일부 컴파일러는 실제로 일련의 조건부 명령어 대신 분기 테이블 또는 인덱싱 된 함수 포인터 배열로 switch 문을 구현할 수 있습니다. 이를 통해 switch 문은 비교 목록을 거치지 않고 실행할 분기를 즉시 결정할 수 있습니다.


4
맞지 않습니다. 케이스 값이 좁거나 넓은 범위에 관계없이 더 빠릅니다. 케이스 값이 얼마나 떨어져 있는지는 중요하지 않습니다.
Aniket Inge

6
@Aniket :이 위키 백과 기사를 읽으십시오. en.wikipedia.org/wiki/
Vishal K

14
@ Nike : 범위가 넓고 드문 ​​경우 O (1)이 아닙니다. 두 종류의 스위치가 있으며 범위가 너무 넓 으면 Java는 "테이블 스위치"가 아닌 "룩업 스위치"로 컴파일합니다. 전자는 발견 될 때까지 분기당 비교가 필요하지만 후자는 그렇지 않습니다.
cHao

4
Wikipedia는 참고 문헌을 찾기에 알맞은 곳이지만 권위있는 출처로 간주해서는 안됩니다. 당신이 읽은 것은 최고의 중고 정보입니다.
cHao

6
@Aniket : 모든 공정에서 해체는 특정 플랫폼의 특정 JVM에 따라 다릅니다. 다른 사람들은 다르게 번역 할 수 있습니다. 실제로 일부는 조회 스위치에 해시 테이블을 사용할 수 있습니다. 여전히 테이블 스위치만큼 성능이 좋지는 않지만 적어도 가깝습니다. JIT에는 시간이 오래 걸리고 입력에 해싱 알고리즘을 적용해야합니다. 따라서 결과 어셈블리 코드가 밝아 질 수 있지만 Windows x86_64의 Hotspot v1.7에 대해 구체적으로 언급하지 않는 한 권위있는 것은 아닙니다.
cHao

30

답은 바이트 코드에 있습니다.

SwitchTest10.java

public class SwitchTest10 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 10: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

해당 바이트 코드; 관련 부품 만 표시 :

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 10
        0: 60;
        1: 70;
        2: 80;
        3: 90;
        4: 100;
        5: 110;
        6: 120;
        7: 131;
        8: 142;
        9: 153;
        10: 164;
        default: 175 }

SwitchTest22.java :

public class SwitchTest22 {

    public static void main(String[] args) {
        int n = 0;

        switcher(n);
    }

    public static void switcher(int n) {
        switch(n) {
            case 0: System.out.println(0);
                    break;

            case 1: System.out.println(1);
                    break;

            case 2: System.out.println(2);
                    break;

            case 3: System.out.println(3);
                    break;

            case 4: System.out.println(4);
                    break;

            case 5: System.out.println(5);
                    break;

            case 6: System.out.println(6);
                    break;

            case 7: System.out.println(7);
                    break;

            case 8: System.out.println(8);
                    break;

            case 9: System.out.println(9);
                    break;

            case 100: System.out.println(10);
                    break;

            case 110: System.out.println(10);
                    break;
            case 120: System.out.println(10);
                    break;
            case 130: System.out.println(10);
                    break;
            case 140: System.out.println(10);
                    break;
            case 150: System.out.println(10);
                    break;
            case 160: System.out.println(10);
                    break;
            case 170: System.out.println(10);
                    break;
            case 180: System.out.println(10);
                    break;
            case 190: System.out.println(10);
                    break;
            case 200: System.out.println(10);
                    break;
            case 210: System.out.println(10);
                    break;

            case 220: System.out.println(10);
                    break;

            default: System.out.println("test");
        }
    }       
}

해당 바이트 코드; 다시, 관련 부품 만 표시 :

public static void switcher(int);
  Code:
   0:   iload_0
   1:   lookupswitch{ //23
        0: 196;
        1: 206;
        2: 216;
        3: 226;
        4: 236;
        5: 246;
        6: 256;
        7: 267;
        8: 278;
        9: 289;
        100: 300;
        110: 311;
        120: 322;
        130: 333;
        140: 344;
        150: 355;
        160: 366;
        170: 377;
        180: 388;
        190: 399;
        200: 410;
        210: 421;
        220: 432;
        default: 443 }

첫 번째 경우 범위가 좁은 경우 컴파일 된 바이트 코드는를 사용합니다 tableswitch. 두 번째 경우, 컴파일 된 바이트 코드는를 사용합니다 lookupswitch.

에서 tableswitch, 스택의 상단에있는 정수 값은 지점 / 점프 대상을 찾기 위해, 테이블에 인덱스로 사용된다. 그런 다음이 점프 / 분기가 즉시 수행됩니다. 따라서 이것은 O(1)작업입니다.

A lookupswitch가 더 복잡합니다. 이 경우 올바른 키를 찾을 때까지 정수 값을 테이블의 모든 키와 비교해야합니다. 키가 발견되면 분기 / 점프 대상 (이 키가 매핑 된)이 점프에 사용됩니다. 사용되는 테이블 lookupswitch이 정렬되고 이진 검색 알고리즘을 사용하여 올바른 키를 찾을 수 있습니다. 이진 검색의 성능은 O(log n), 전체 과정은 또한 O(log n)점프가 여전히 있기 때문에 O(1). 따라서 희소 범위의 경우 성능이 저하되는 이유는 테이블에 직접 색인을 작성할 수 없기 때문에 올바른 키를 먼저 찾아야하기 때문입니다.

스파 스 값이 있고 tableswitch사용할 수있는 경우 테이블에는 기본적으로 default옵션 을 가리키는 더미 항목이 포함 됩니다. 예를 들어의 마지막 항목 SwitchTest10.java21대신에 있다고 가정하면 10다음과 같은 결과가 나타납니다.

public static void switcher(int);
  Code:
   0:   iload_0
   1:   tableswitch{ //0 to 21
        0: 104;
        1: 114;
        2: 124;
        3: 134;
        4: 144;
        5: 154;
        6: 164;
        7: 175;
        8: 186;
        9: 197;
        10: 219;
        11: 219;
        12: 219;
        13: 219;
        14: 219;
        15: 219;
        16: 219;
        17: 219;
        18: 219;
        19: 219;
        20: 219;
        21: 208;
        default: 219 }

따라서 컴파일러는 기본적으로 간격 사이의 더미 항목을 포함하는 거대한 테이블을 작성하여 default명령 의 분기 대상을 가리 킵니다 . 이 없더라도 스위치 블록 default명령 가리키는 항목이 포함됩니다 . 몇 가지 기본 테스트를 수행했으며 마지막 인덱스와 이전 인덱스 사이의 간격 9이보다 크면을 대신 35사용 lookupswitch합니다 tableswitch.

switch명령문 의 동작은 Java Virtual Machine Specification (§3.10)에 정의되어 있습니다 .

스위치의 경우가 드문 경우, 테이블 스위치 명령의 테이블 표현은 공간 측면에서 비효율적입니다. lookupswitch 명령이 대신 사용될 수 있습니다. lookupswitch 명령어는 테이블의 대상 오프셋과 int 키 (케이스 레이블 값)를 쌍으로합니다. 조회 스위치 명령이 실행될 때 스위치 식의 값이 테이블의 키와 비교됩니다. 키 중 하나가 표현식의 값과 일치하면 연관된 대상 오프셋에서 실행이 계속됩니다. 일치하는 키가 없으면 기본 대상에서 실행이 계속됩니다. [...]


1
나는 숫자가 항상 연속적이지만 범위는 다소 길다는 질문에서 이해했다.
assylias

@assylias 흠, 흥미로운. 나는 그 질문을 오해 한 것 같다. 좀 더 실험 해 보도록하겠습니다. 그래서 0-30 의 연속 범위 에서도 컴파일러는 lookupswitch?
Vivin Paliath

@VivinPaliath : 예, 테스트에서 케이스 상수는 항상 인접하므로 기본적으로 [0, 1], [0, 1, 2], [0, 1, 2, 3] ...에서 스위치를 테스트하고 있습니다.
Andrew Bissell

@VivinPaliath 아니요, 바이트 코드는 항상 테이블 스위치를 사용하지만 JIT 컴파일러는 포함 된 항목 수에 따라 동일한 방식으로 테이블 스위치를 컴파일하지 않는 것처럼 보입니다.
assylias

6
@VivinPaliath 나는 확실히 그 질문을 더 명확하게 말할 수 있었다. 이 저수준 바이트 코드 및 어셈블리 관련 응답을 평가할 때 깊이 빠져 있습니다. 여전히 테이블 스위치 / 조회 스위치 구별이 실제로 중요한 것처럼 보이며, 지금까지 그 용어를 사용하는 유일한 대답은 귀하의 유일한 대답입니다 (다른 용어는 아마도 다른 용어로 동일한 개념을 설정하고 있음). 또한 JVM 사양 링크를 좋아합니다.
Andrew Bissell

19

질문에 이미 답변이되었으므로 여기에 몇 가지 팁이 있습니다. 사용하다

private static final double[] mul={1d, 10d...};
static double multiplyByPowerOfTen(final double d, final int exponent) {
      if (exponent<0 || exponent>=mul.length) throw new ParseException();//or just leave the IOOBE be
      return mul[exponent]*d;
}

이 코드는 훨씬 적은 IC (명령 캐시)를 사용하며 항상 인라인됩니다. 코드가 핫하면 어레이는 L1 데이터 캐시에있게됩니다. 룩업 테이블은 거의 항상 승리입니다. (마이크로 벤치 마크 : D)

편집 : 방법을 핫 인라인으로 만들려면 빠른 경로가 아닌 경로 throw new ParseException()를 최소 길이로 생각하거나 별도의 정적 메서드로 이동하여 최소 길이로 만드십시오. 그것은 throw new ParseException("Unhandled power of ten " + power, 0);해석하기 쉬운 코드에 대한 많은 인라이닝 예산을 먹는 약한 아이디어입니다. 문자열 연결은 바이트 코드에서 매우 장황합니다. 더 많은 정보와 ArrayList가 있는 실제 사례

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