JDK 코드를 실행할 때 Java JIT가 부정 행위를합니까?


405

나는 일부 코드를 벤치마킹하고 있었고 java.math.BigInteger정확히 동일한 알고리즘을 사용할 때조차 와 같이 빨리 실행할 수 없었습니다 . 그래서 java.math.BigInteger소스를 내 패키지에 복사 하고 다음을 시도했습니다.

//import java.math.BigInteger;

public class MultiplyTest {
    public static void main(String[] args) {
        Random r = new Random(1);
        long tm = 0, count = 0,result=0;
        for (int i = 0; i < 400000; i++) {
            int s1 = 400, s2 = 400;
            BigInteger a = new BigInteger(s1 * 8, r), b = new BigInteger(s2 * 8, r);
            long tm1 = System.nanoTime();
            BigInteger c = a.multiply(b);
            if (i > 100000) {
                tm += System.nanoTime() - tm1;
                count++;
            }
            result+=c.bitLength();
        }
        System.out.println((tm / count) + "nsec/mul");
        System.out.println(result); 
    }
}

이것을 실행하면 (MacOS의 경우 JDBC 1.8.0_144-b01) 다음과 같이 출력됩니다.

12089nsec/mul
2559044166

가져 오기 줄을 주석 처리하지 않고 실행할 때 :

4098nsec/mul
2559044166

JDK 버전의 BigInteger를 내 버전과 비교할 때 동일한 코드를 사용하더라도 거의 3 배 빠릅니다.

javap로 바이트 코드를 검사하고 옵션으로 실행할 때 컴파일러 출력을 비교했습니다.

-Xbatch -XX:-TieredCompilation -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions 
-XX:+PrintInlining -XX:CICompilerCount=1

두 버전 모두 동일한 코드를 생성하는 것 같습니다. 핫스팟은 코드에서 사용할 수없는 사전 계산 된 최적화를 사용합니까? 나는 항상 그렇지 않다는 것을 이해했습니다. 이 차이점을 설명하는 것은 무엇입니까?


29
흥미 롭군 1. 결과가 일관됩니까 (또는 운이 좋은 무작위입니까)? 2. JVM 예열 후 시도해 볼 수 있습니까? 3. 임의 인자를 제거하고 두 테스트 모두에 대한 입력과 동일한 데이터 세트를 제공 할 수 있습니까?
Jigar Joshi

7
JMH openjdk.java.net/projects/code-tools/jmh로 벤치 마크를 실행 해 보셨습니까 ? 수동으로 정확하게 측정하는 것은 쉽지 않습니다 (따뜻한 모든 것).
Roman Puchkovskiy

2
예, 매우 일관성이 있습니다. 10 분 동안 실행 시키면 여전히 동일한 차이가 발생합니다. 고정 랜덤 시드는 두 실행 모두 동일한 데이터 세트를 얻습니다.
Koen Hendrikx '

5
만일을 대비하여 JMH를 원할 것입니다. 또한 수정 된 BigInteger를 어딘가에 배치하여 사람들이 테스트를 재현하고 실행중인 것으로 판단하는지 확인할 수 있습니다.
pvg

답변:


529

예, HotSpot JVM은 일종의 "속임수" BigInteger입니다. Java 코드에서는 찾을 수없는 일부 메소드 의 특수 버전이 있기 때문 입니다. 이러한 메소드를 JVM 내장 함수 라고 합니다.

특히, BigInteger.multiplyToLenHotSpot의 고유 한 방법입니다. JVM 소스 기반 에는 특수한 핸드 코딩 된 어셈블리 구현 이 있지만 x86-64 아키텍처에만 해당됩니다.

-XX:-UseMultiplyToLenIntrinsicJVM이 순수한 Java 구현을 사용하도록 강제 하는 옵션 으로이 내장 기능을 사용하지 않을 수 있습니다 . 이 경우 성능은 복사 된 코드의 성능과 유사합니다.

PS는 여기있다 목록 다른 핫스팟 극한 방법.


141

Java 8 에서는 이것이 본질적인 방법입니다. 약간 수정 된 버전의 메소드 :

 private static BigInteger test() {

    Random r = new Random(1);
    BigInteger c = null;
    for (int i = 0; i < 400000; i++) {
        int s1 = 400, s2 = 400;
        BigInteger a = new BigInteger(s1 * 8, r), b = new BigInteger(s2 * 8, r);
        c = a.multiply(b);
    }
    return c;
}

이것을 사용하여 실행 :

 java -XX:+UnlockDiagnosticVMOptions  
      -XX:+PrintInlining 
      -XX:+PrintIntrinsics 
      -XX:CICompilerCount=2 
      -XX:+PrintCompilation   
       <YourClassName>

이것은 많은 줄을 인쇄하고 그중 하나는 다음과 같습니다.

 java.math.BigInteger::multiplyToLen (216 bytes)   (intrinsic)

반면에 Java 9 에서는 해당 메소드가 더 이상 고유하지 않은 것으로 보이지만 결국 고유 한 메소드를 호출합니다.

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

따라서 Java 9에서 동일한 매개 변수를 사용하여 동일한 코드를 실행하면 다음이 표시됩니다.

java.math.BigInteger::implMultiplyToLen (216 bytes)   (intrinsic)

그 아래에는 메서드와 동일한 코드가 있습니다. 이름이 약간 다릅니다.

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