source

x64 Java에서 int보다 긴 이유는 무엇입니까?

itover 2022. 11. 2. 21:35
반응형

x64 Java에서 int보다 긴 이유는 무엇입니까?

Surface Pro 2 태블릿에서 Java 7 업데이트 45 x 64(32비트 Java 미설치)를 사용하여 Windows 8.1 x 64를 실행하고 있습니다.

아래 코드는 i타입이 long일 경우 1688ms, int일 경우 109ms가 소요됩니다.64비트 JVM을 사용하는 64비트 플랫폼에서 롱(64비트 타입)이 int보다 느린 이유는 무엇입니까?

CPU가 32비트 정수보다 64비트 정수를 추가하는 데 더 오래 걸릴 것으로 생각되지만 그럴 가능성은 거의 없습니다.해스웰은 리플 캐리어 애더를 사용하지 않는 것 같아요.

이클립스 케플러 SR1, BTW에서 실행 중입니다.

public class Main {

    private static long i = Integer.MAX_VALUE;

    public static void main(String[] args) {    
        System.out.println("Starting the loop");
        long startTime = System.currentTimeMillis();
        while(!decrementAndCheck()){
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Finished the loop in " + (endTime - startTime) + "ms");
    }

    private static boolean decrementAndCheck() {
        return --i < 0;
    }

}

편집: VS 2013(아래)에서 컴파일한 동일 시스템 C++ 코드의 결과는 다음과 같습니다.long: 72265ms int: 74656ms 이러한 결과는 debug 32비트모드입니다

64비트 릴리즈 모드: 롱: 875ms 길이: 906ms int: 1047ms

이는 CPU 제한이 아니라 JVM 최적화가 이상하다는 것을 보여줍니다.

#include "stdafx.h"
#include "iostream"
#include "windows.h"
#include "limits.h"

long long i = INT_MAX;

using namespace std;


boolean decrementAndCheck() {
return --i < 0;
}


int _tmain(int argc, _TCHAR* argv[])
{


cout << "Starting the loop" << endl;

unsigned long startTime = GetTickCount64();
while (!decrementAndCheck()){
}
unsigned long endTime = GetTickCount64();

cout << "Finished the loop in " << (endTime - startTime) << "ms" << endl;



}

편집: Java 8 RTM에서 다시 시도했지만 큰 변화는 없습니다.

하면 JVM은 작업을 합니다.longs:

0x00007fdd859dbb80: test   %eax,0x5f7847a(%rip)  /* fun JVM hack */
0x00007fdd859dbb86: dec    %r11                  /* i-- */
0x00007fdd859dbb89: mov    %r11,0x258(%r10)      /* store i to memory */
0x00007fdd859dbb90: test   %r11,%r11             /* unnecessary test */
0x00007fdd859dbb93: jge    0x00007fdd859dbb80    /* go back to the loop top */

, , it, it가 it it it you 를 사용하면잘 되지 않습니다.int수 몇

0x00007f3dc290b5a1: mov    %r11d,%r9d
0x00007f3dc290b5a4: dec    %r9d
0x00007f3dc290b5a7: mov    %r9d,0x258(%r10)
0x00007f3dc290b5ae: test   %r9d,%r9d
0x00007f3dc290b5b1: jl     0x00007f3dc290b662
0x00007f3dc290b5b7: add    $0xfffffffffffffffe,%r11d
0x00007f3dc290b5bb: mov    %r9d,%ecx
0x00007f3dc290b5be: dec    %ecx              
0x00007f3dc290b5c0: mov    %ecx,0x258(%r10)   
0x00007f3dc290b5c7: cmp    %r11d,%ecx
0x00007f3dc290b5ca: jle    0x00007f3dc290b5d1
0x00007f3dc290b5cc: mov    %ecx,%r9d
0x00007f3dc290b5cf: jmp    0x00007f3dc290b5bb
0x00007f3dc290b5d1: and    $0xfffffffffffffffe,%r9d
0x00007f3dc290b5d5: mov    %r9d,%r8d
0x00007f3dc290b5d8: neg    %r8d
0x00007f3dc290b5db: sar    $0x1f,%r8d
0x00007f3dc290b5df: shr    $0x1f,%r8d
0x00007f3dc290b5e3: sub    %r9d,%r8d
0x00007f3dc290b5e6: sar    %r8d
0x00007f3dc290b5e9: neg    %r8d
0x00007f3dc290b5ec: and    $0xfffffffffffffffe,%r8d
0x00007f3dc290b5f0: shl    %r8d
0x00007f3dc290b5f3: mov    %r8d,%r11d
0x00007f3dc290b5f6: neg    %r11d
0x00007f3dc290b5f9: sar    $0x1f,%r11d
0x00007f3dc290b5fd: shr    $0x1e,%r11d
0x00007f3dc290b601: sub    %r8d,%r11d
0x00007f3dc290b604: sar    $0x2,%r11d
0x00007f3dc290b608: neg    %r11d
0x00007f3dc290b60b: and    $0xfffffffffffffffe,%r11d
0x00007f3dc290b60f: shl    $0x2,%r11d
0x00007f3dc290b613: mov    %r11d,%r9d
0x00007f3dc290b616: neg    %r9d
0x00007f3dc290b619: sar    $0x1f,%r9d
0x00007f3dc290b61d: shr    $0x1d,%r9d
0x00007f3dc290b621: sub    %r11d,%r9d
0x00007f3dc290b624: sar    $0x3,%r9d
0x00007f3dc290b628: neg    %r9d
0x00007f3dc290b62b: and    $0xfffffffffffffffe,%r9d
0x00007f3dc290b62f: shl    $0x3,%r9d
0x00007f3dc290b633: mov    %ecx,%r11d
0x00007f3dc290b636: sub    %r9d,%r11d
0x00007f3dc290b639: cmp    %r11d,%ecx
0x00007f3dc290b63c: jle    0x00007f3dc290b64f
0x00007f3dc290b63e: xchg   %ax,%ax /* OK, fine; I know what a nop looks like */

풀린 루프 자체:

0x00007f3dc290b640: add    $0xfffffffffffffff0,%ecx
0x00007f3dc290b643: mov    %ecx,0x258(%r10)
0x00007f3dc290b64a: cmp    %r11d,%ecx
0x00007f3dc290b64d: jg     0x00007f3dc290b640

다음으로 롤링되지 않은 루프에 대한 해체 코드(테스트 및 스트레이트 루프)를 나타냅니다.

0x00007f3dc290b64f: cmp    $0xffffffffffffffff,%ecx
0x00007f3dc290b652: jle    0x00007f3dc290b662
0x00007f3dc290b654: dec    %ecx
0x00007f3dc290b656: mov    %ecx,0x258(%r10)
0x00007f3dc290b65d: cmp    $0xffffffffffffffff,%ecx
0x00007f3dc290b660: jg     0x00007f3dc290b654

가 ints를 풀었기 .int했지만 '16번'은.long전혀 루프하지 않습니다.

완전성을 위해 실제로 시도한 코드는 다음과 같습니다.

public class foo136 {
  private static int i = Integer.MAX_VALUE;
  public static void main(String[] args) {
    System.out.println("Starting the loop");
    for (int foo = 0; foo < 100; foo++)
      doit();
  }

  static void doit() {
    i = Integer.MAX_VALUE;
    long startTime = System.currentTimeMillis();
    while(!decrementAndCheck()){
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Finished the loop in " + (endTime - startTime) + "ms");
  }

  private static boolean decrementAndCheck() {
    return --i < 0;
  }
}

는 옵션 ""를 되었습니다.-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssemblyJVM의 인스톨도 조작할 필요가 있는 것에 주의해 주세요.랜덤 공유 라이브러리를 올바른 장소에 배치하지 않으면 실패합니다.

JVM 스택은 단어 단위로 정의되며, 그 크기는 구현 세부 사항이지만 폭이 32비트 이상이어야 합니다.JVM 구현자는 64비트 단어를 사용할 수 있지만 바이트코드는 이에 의존할 수 없기 때문에 다음과 같은 작업을 수행할 수 없습니다.long ★★★★★★★★★★★★★★★★★」double값은 특별히 주의해서 다루어야 합니다.특히 JVM 정수 브랜치명령어는 정확히 다음 유형에 정의되어 있습니다.int.

코드의 경우 분해가 도움이 됩니다. 여기 , 라고 하다의 입니다.int7에 : Oracle JDK 7:

private static boolean decrementAndCheck();
  Code:
     0: getstatic     #14  // Field i:I
     3: iconst_1      
     4: isub          
     5: dup           
     6: putstatic     #14  // Field i:I
     9: ifge          16
    12: iconst_1      
    13: goto          17
    16: iconst_0      
    17: ireturn       

JVM의 합니다.i ( 스택(하여 (6 넣습니다. (0) 1은 (3-4)는 (6)는 (6)는 (6)은 (6)은 (6)은 (6)은 (으)로 합니다.그런 다음 0과 비교 분기가 실행되어 돌아갑니다.

" " 가 long조금 더 복잡합니다.

private static boolean decrementAndCheck();
  Code:
     0: getstatic     #14  // Field i:J
     3: lconst_1      
     4: lsub          
     5: dup2          
     6: putstatic     #14  // Field i:J
     9: lconst_0      
    10: lcmp          
    11: ifge          18
    14: iconst_1      
    15: goto          19
    18: iconst_0      
    19: ireturn       

먼저 JVM이 스택(5)에서 새 값을 복제할 때 두 개의 스택 단어를 복제해야 합니다.JVM은 편리한 경우 64비트 단어를 자유롭게 사용할 수 있기 때문에 복제하는 것보다 비용이 많이 들지 않을 수 있습니다.다만, 브랜치 로직이 길어지고 있는 것을 알 수 있습니다.JVM을 .long붙여서 0을 .0L에 대해 인 '9'를 실행합니다.long비교(10) 후 해당 계산 값을 분기합니다.

다음으로 두 가지 가능성이 있는 시나리오를 제시하겠습니다.

  • JVM은 바이트 코드 경로를 정확히 따르고 있습니다.더 요.long버전, 푸시 및 팝핑된 몇 가지 추가 값이 실제 하드웨어 지원 CPU 스택이 아닌 가상 관리 스택에 있습니다.이 경우에도 워밍업 후에도 성능 차이가 크게 나타납니다.
  • JVM은 이 코드를 최적화할 수 있음을 인식합니다.이 경우 실제로 불필요한 푸시/비교 논리의 일부를 최적화하려면 시간이 더 걸립니다.이 경우 워밍업 후 성능 차이는 거의 없습니다.

올바른 마이크로벤치마크를 작성하여 JIT가 제로가 아닌 최종 조건으로 이 테스트를 실시하여 JVM을 강제로 비교하도록 할 것을 권장합니다.intlong.

Java Virtual Machine의 기본 데이터 단위는 word입니다.적절한 워드 사이즈를 선택하는 것은 JVM의 실장 후에 남습니다.JVM 구현에서는 최소 32비트의 워드사이즈를 선택해야 합니다.더 큰 단어 크기를 선택하여 효율성을 높일 수 있습니다.64비트 JVM이 64비트 워드만을 선택해야 한다는 제한도 없습니다.

기본 아키텍처에서는 단어 크기도 동일해야 한다고 규정하지 않습니다.JVM은 데이터를 한 단어씩 읽고 씁니다.이것이 int보다 시간이 오래 걸릴 수 있는 이유입니다.

여기에서 같은 주제에 대한 자세한 내용을 찾을 수 있습니다.

방금 캘리퍼로 벤치마크를 작성했습니다.

결과는 원래 코드와 상당히 일치합니다.사용 시 속도가 최대 12배 향상됩니다.int에 걸쳐서longtmyklebu 등이 보고한 루프 언롤링이 진행되고 있는 것 같습니다.

timeIntDecrements         195,266,845.000
timeLongDecrements      2,321,447,978.000

이 제이 사용되고 .이 코드는 새로 빌드된caliper기존 베타 릴리즈에 대해 어떻게 코드를 작성해야 할지 알 수 없었기 때문입니다.

package test;

import com.google.caliper.Benchmark;
import com.google.caliper.Param;

public final class App {

    @Param({""+1}) int number;

    private static class IntTest {
        public static int v;
        public static void reset() {
            v = Integer.MAX_VALUE;
        }
        public static boolean decrementAndCheck() {
            return --v < 0;
        }
    }

    private static class LongTest {
        public static long v;
        public static void reset() {
            v = Integer.MAX_VALUE;
        }
        public static boolean decrementAndCheck() {
            return --v < 0;
        }
    }

    @Benchmark
    int timeLongDecrements(int reps) {
        int k=0;
        for (int i=0; i<reps; i++) {
            LongTest.reset();
            while (!LongTest.decrementAndCheck()) { k++; }
        }
        return (int)LongTest.v | k;
    }    

    @Benchmark
    int timeIntDecrements(int reps) {
        int k=0;
        for (int i=0; i<reps; i++) {
            IntTest.reset();
            while (!IntTest.decrementAndCheck()) { k++; }
        }
        return IntTest.v | k;
    }
}

참고로 이 버전은 대략적인 "예열"을 수행합니다.

public class LongSpeed {

    private static long i = Integer.MAX_VALUE;
    private static int j = Integer.MAX_VALUE;

    public static void main(String[] args) {

        for (int x = 0; x < 10; x++) {
            runLong();
            runWord();
        }
    }

    private static void runLong() {
        System.out.println("Starting the long loop");
        i = Integer.MAX_VALUE;
        long startTime = System.currentTimeMillis();
        while(!decrementAndCheckI()){

        }
        long endTime = System.currentTimeMillis();

        System.out.println("Finished the long loop in " + (endTime - startTime) + "ms");
    }

    private static void runWord() {
        System.out.println("Starting the word loop");
        j = Integer.MAX_VALUE;
        long startTime = System.currentTimeMillis();
        while(!decrementAndCheckJ()){

        }
        long endTime = System.currentTimeMillis();

        System.out.println("Finished the word loop in " + (endTime - startTime) + "ms");
    }

    private static boolean decrementAndCheckI() {
        return --i < 0;
    }

    private static boolean decrementAndCheckJ() {
        return --j < 0;
    }

}

전체 시간은 약 30% 개선되지만, 둘 사이의 비율은 거의 동일합니다.

기록의 경우:

내가 사용한다면

boolean decrementAndCheckLong() {
    lo = lo - 1l;
    return lo < -1l;
}

("l--" ~ "l = l - 1l") 긴 성능 최대 50% 향상

이는 JVM이 long을 사용할 때(카운트되지 않은 루프), int(카운트되지 않은 루프)에 대해 세이프 포인트를 체크하기 때문일 수 있습니다.

참고 자료: https://stackoverflow.com/a/62557768/14624235

https://stackoverflow.com/a/58726530/14624235

http://psy-lob-saw.blogspot.com/2016/02/wait-for-it-counteduncounted-loops.html

테스트할 수 있는 64비트 머신은 없지만, 다소 큰 차이는 작업 중인 바이트 코드보다 약간 긴 바이트 코드가 더 많은 바이트 코드가 있음을 나타냅니다.

32비트 1.7.0_45에서는 long/int(4400ms와 4800ms)에 매우 가까운 시간이 표시됩니다.

추측에 불과하지만 메모리 미스 얼라인먼트 패널티의 영향이라고 생각합니다. 의심을 확인/확정하려면 i 선언 전에 public static int dummy = 0을 추가해 봅니다. 그러면 메모리 레이아웃에서 i가 4바이트 내려가고 성능이 향상될 수 있습니다.문제의 원인이 아님을 확인.

편집: 최적의 정렬을 위해 VM이 여유롭게 필드를 재정렬할 없기 때문에 JNI에 방해가 될 수 있습니다(그렇지 않습니다).

언급URL : https://stackoverflow.com/questions/19844048/why-is-long-slower-than-int-in-x64-java

반응형