Java JIT có gian lận khi chạy mã JDK không?


405

Tôi đã điểm chuẩn một số mã và tôi không thể chạy mã nhanh như vậy java.math.BigInteger, ngay cả khi sử dụng cùng một thuật toán. Vì vậy, tôi đã sao chép java.math.BigIntegernguồn vào gói của riêng mình và thử điều này:

//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); 
    }
}

Khi tôi chạy cái này (jdk 1.8.0_144-b01 trên MacOS), nó xuất ra:

12089nsec/mul
2559044166

Khi tôi chạy nó với dòng nhập không bị lỗi:

4098nsec/mul
2559044166

Nó nhanh gấp gần ba lần khi sử dụng phiên bản JDK của BigInteger so với phiên bản của tôi, ngay cả khi nó sử dụng cùng một mã.

Tôi đã kiểm tra mã byte bằng javap và so sánh đầu ra của trình biên dịch khi chạy với các tùy chọn:

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

và cả hai phiên bản dường như tạo ra cùng một mã. Vì vậy, hotspot có sử dụng một số tối ưu hóa được tính toán trước mà tôi không thể sử dụng trong mã của mình không? Tôi luôn hiểu rằng họ không. Điều gì giải thích sự khác biệt này?


29
Hấp dẫn. 1. Là kết quả phù hợp (hoặc chỉ là may mắn ngẫu nhiên)? 2. Bạn có thể thử sau khi làm nóng JVM không? 3. Bạn có thể loại bỏ yếu tố ngẫu nhiên và cung cấp cùng một tập dữ liệu làm đầu vào cho cả hai bài kiểm tra không?
Jigar Joshi

7
Bạn đã thử chạy điểm chuẩn của mình với JMH openjdk.java.net/projects/code-tools/jmh chưa? Không dễ để thực hiện các phép đo một cách chính xác bằng tay (làm nóng và tất cả những thứ đó).
La Mã Puchkovskiy

2
Vâng, nó rất phù hợp. Nếu tôi để nó chạy trong 10 phút tôi vẫn nhận được sự khác biệt tương tự. Hạt giống ngẫu nhiên cố định đảm bảo rằng cả hai lần chạy đều có cùng một tập dữ liệu.
Koen Hendrikx

5
Bạn có thể vẫn muốn JMH, chỉ trong trường hợp. Và bạn nên đặt BigInteger đã sửa đổi của mình ở đâu đó để mọi người có thể sao chép bài kiểm tra của bạn và xác minh bạn đang chạy những gì bạn nghĩ bạn đang chạy.
pvg

Câu trả lời:


529

Đúng, HotSpot JVM là loại "gian lận", bởi vì nó có phiên bản đặc biệt của một số BigIntegerphương thức mà bạn sẽ không tìm thấy trong mã Java. Các phương thức này được gọi là nội tại JVM .

Đặc biệt, BigInteger.multiplyToLenlà một phương thức Barsinsic trong HotSpot. Có một triển khai lắp ráp được mã hóa đặc biệt trong cơ sở nguồn JVM, nhưng chỉ dành cho kiến ​​trúc x86-64.

Bạn có thể vô hiệu hóa Barsinsic này với -XX:-UseMultiplyToLenIntrinsictùy chọn để buộc JVM sử dụng triển khai Java thuần túy. Trong trường hợp này, hiệu suất sẽ tương tự như hiệu suất của mã được sao chép của bạn.

PS Dưới đây là danh sách các phương thức nội tại HotSpot khác.


141

Trong Java 8 đây thực sự là một phương thức nội tại; một phiên bản sửa đổi một chút của phương thức:

 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;
}

Chạy này với:

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

Điều này sẽ in rất nhiều dòng và một trong số họ sẽ là:

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

Mặt khác, trong Java 9 , phương thức đó dường như không còn là nội tại nữa, nhưng đến lượt nó lại gọi một phương thức là nội tại:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

Vì vậy, việc chạy cùng một mã theo Java 9 (có cùng tham số) sẽ tiết lộ:

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

Bên dưới nó là cùng một mã cho phương thức - chỉ là một cách đặt tên hơi khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.