Perbedaan besar dalam kecepatan metode statik dan non statik ekivalen

86

Dalam kode ini ketika saya membuat Objek dalam mainmetode dan kemudian memanggil metode objek: ff.twentyDivCount(i)(berjalan dalam 16010 ms), itu berjalan jauh lebih cepat daripada memanggilnya menggunakan anotasi ini: twentyDivCount(i)(berjalan dalam 59516 ms). Tentu saja, ketika saya menjalankannya tanpa membuat objek, saya membuat metode ini statis, sehingga bisa dipanggil di main.

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {    // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way
                       // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

EDIT: Sejauh ini tampaknya mesin yang berbeda menghasilkan hasil yang berbeda, tetapi menggunakan JRE 1.8. * Adalah saat hasil aslinya tampak direproduksi secara konsisten.

Stabbz
sumber
4
Bagaimana Anda menjalankan benchmark Anda? Saya berani bertaruh bahwa ini adalah artefak JVM yang tidak memiliki cukup waktu untuk mengoptimalkan kode.
Patrick Collins
2
Sepertinya sudah cukup waktu bagi JVM untuk mengkompilasi dan melakukan OSR untuk metode utama seperti yang +PrintCompilation +PrintInliningditunjukkan
Tagir Valeev
1
Saya telah mencoba cuplikan kode tersebut, tetapi saya tidak mendapatkan perbedaan waktu seperti yang dikatakan Stabbz. Mereka 56282ms (menggunakan instance) 54551ms (sebagai metode statis).
Don Chakkappan
1
@PatrickCollins Lima detik sudah cukup. Saya menulis ulang sedikit sehingga Anda dapat mengukur keduanya (satu JVM dimulai per varian). Saya tahu bahwa sebagai patokan itu masih cacat, tetapi cukup meyakinkan: 1457 ms STATIC vs 5312 ms NON_STATIC.
maaartinus
1
Belum menyelidiki pertanyaan itu secara mendetail, tapi ini mungkin terkait: shipilev.net/blog/2015/black-magic-method-dispatch (mungkin Aleksey Shipilëv dapat mencerahkan kita di sini)
Marco13

Jawaban:

72

Menggunakan JRE 1.8.0_45 saya mendapatkan hasil yang serupa.

Penyelidikan:

  1. menjalankan java dengan -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInliningopsi VM menunjukkan bahwa kedua metode dikompilasi dan disisipkan
  2. Melihat perakitan yang dihasilkan untuk metode itu sendiri tidak menunjukkan perbedaan yang signifikan
  3. Namun, begitu mereka menjadi sebaris, perakitan yang dihasilkan di dalamnya mainsangat berbeda, dengan metode instans dioptimalkan secara lebih agresif, terutama dalam hal membuka gulungan loop

Saya kemudian menjalankan pengujian Anda lagi tetapi dengan pengaturan loop unrolling yang berbeda untuk mengkonfirmasi kecurigaan di atas. Saya menjalankan kode Anda dengan:

  • -XX:LoopUnrollLimit=0 dan kedua metode berjalan lambat (mirip dengan metode statis dengan opsi default).
  • -XX:LoopUnrollLimit=100 dan kedua metode berjalan cepat (mirip dengan metode instance dengan opsi default).

Sebagai kesimpulan tampaknya, dengan pengaturan default, JIT dari hotspot 1.8.0_45 tidak dapat membuka gulungan ketika metode statis (meskipun saya tidak yakin mengapa berperilaku seperti itu). JVM lain mungkin memberikan hasil yang berbeda.

assylias
sumber
Antara 52 dan 71, perilaku asli dipulihkan (setidaknya di komputer saya, jawaban saya). Sepertinya versi statisnya 20 unit lebih besar, tapi kenapa? Ini aneh.
maaartinus
3
@maaartinus Saya bahkan tidak yakin apa yang diwakili oleh angka itu dengan tepat - doc cukup mengelak: "Buka gulungan badan loop dengan jumlah node representasi perantara kompiler server kurang dari nilai ini. Batas yang digunakan oleh kompilator server adalah fungsi dari nilai ini, bukan nilai sebenarnya . Nilai default bervariasi dengan platform tempat JVM dijalankan. "...
assylias
Tidak ada yang saya tahu, tetapi tebakan pertama saya adalah bahwa metode statis menjadi sedikit lebih besar dalam unit apa pun dan bahwa kami mencapai tempat yang penting. Namun, perbedaannya cukup besar, jadi tebakan saya saat ini adalah bahwa versi statis mendapatkan beberapa pengoptimalan yang membuatnya sedikit lebih besar. Saya belum melihat asm yang dihasilkan.
maaartinus
33

Hanya tebakan yang belum terbukti berdasarkan jawaban assylias.

JVM menggunakan ambang untuk membuka gulungan loop, yang kira-kira 70. Untuk alasan apa pun, panggilan statis sedikit lebih besar dan tidak dapat dibuka.

Perbarui hasil

  • Dengan LoopUnrollLimitdi bawah 52, kedua versi lambat.
  • Antara 52 dan 71, hanya versi statis yang lambat.
  • Di atas 71, kedua versi itu cepat.

Ini aneh karena dugaan saya adalah bahwa panggilan statis hanya sedikit lebih besar di representasi internal dan OP mengalami kasus yang aneh. Tetapi perbedaannya tampaknya sekitar 20, yang tidak masuk akal.

 

-XX:LoopUnrollLimit=51
5400 ms NON_STATIC
5310 ms STATIC
-XX:LoopUnrollLimit=52
1456 ms NON_STATIC
5305 ms STATIC
-XX:LoopUnrollLimit=71
1459 ms NON_STATIC
5309 ms STATIC
-XX:LoopUnrollLimit=72
1457 ms NON_STATIC
1488 ms STATIC

Bagi yang mau bereksperimen, versi saya semoga bermanfaat.

maaartinus
sumber
Apakah waktu '1456 ms'? Jika demikian, mengapa Anda mengatakan statis lambat?
Tony
@Tony saya bingung NON_STATICdan STATIC, tapi kesimpulan saya benar. Diperbaiki sekarang, terima kasih.
maaartinus
0

Ketika ini dijalankan dalam mode debug, jumlahnya sama untuk instance dan kasus statis. Itu lebih lanjut berarti bahwa JIT ragu-ragu untuk mengkompilasi kode ke kode asli dalam kasus statis dengan cara yang sama seperti yang dilakukannya dalam kasus metode instance.

Mengapa demikian? Sulit untuk mengatakannya; mungkin itu akan melakukan hal yang benar jika ini adalah aplikasi yang lebih besar ...

Dragan Bozanovic
sumber
"Mengapa melakukannya? Sulit dikatakan, mungkin itu akan melakukan hal yang benar jika ini adalah aplikasi yang lebih besar." Atau Anda hanya akan mengalami masalah kinerja aneh yang terlalu besar untuk di-debug. (Dan tidak sulit untuk mengatakannya. Anda dapat melihat perakitan yang dimuntahkan JVM seperti yang dilakukan assylias.)
tmyklebu
@tmyklebu Atau kami memiliki masalah kinerja aneh yang tidak perlu dan mahal untuk di-debug sepenuhnya dan ada solusi yang mudah. Pada akhirnya, kita berbicara tentang JIT di sini, penulisnya tidak tahu persis bagaimana perilakunya dalam semua situasi. :) Lihatlah jawaban lainnya, mereka sangat baik dan sangat dekat untuk menjelaskan masalahnya, tetapi sejauh ini tidak ada yang tahu mengapa hal ini terjadi.
Dragan Bozanovic
@DraganBozanovic: Ini berhenti menjadi "tidak perlu men-debug sepenuhnya" ketika itu menyebabkan masalah nyata dalam kode nyata.
tmyklebu
0

Saya hanya mengubah tes sedikit dan saya mendapatkan hasil sebagai berikut:

Keluaran:

Dynamic Test:
465585120
232792560
232792560
51350 ms
Static Test:
465585120
232792560
232792560
52062 ms

CATATAN

Saat saya mengujinya secara terpisah, saya mendapat ~ 52 detik untuk dinamis dan ~ 200 detik untuk statis.

Ini programnya:

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {  // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    static int twentyDivCount2(int a) {
         int count = 0;
         for (int i = 1; i<21; i++) {

             if (a % i == 0) {
                 count++;
             }
         }
         return count;
    }

    public static void main(String[] args) {
        System.out.println("Dynamic Test: " );
        dynamicTest();
        System.out.println("Static Test: " );
        staticTest();
    }

    private static void staticTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        for (int i = start; i > 0; i--) {

            int temp = twentyDivCount2(i);

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }

    private static void dynamicTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

Saya juga mengubah urutan tes menjadi:

public static void main(String[] args) {
    System.out.println("Static Test: " );
    staticTest();
    System.out.println("Dynamic Test: " );
    dynamicTest();
}

Dan saya mendapatkan ini:

Static Test:
465585120
232792560
232792560
188945 ms
Dynamic Test:
465585120
232792560
232792560
50106 ms

Seperti yang Anda lihat, jika dinamika dipanggil sebelum statis, kecepatan statis menurun secara dramatis.

Berdasarkan tolok ukur ini:

Saya berhipotesis bahwa semuanya tergantung pada pengoptimalan JVM. jadi saya hanya menyarankan Anda untuk menggunakan aturan praktis untuk penggunaan metode statis dan dinamis.

ATURAN DEMAM:

Java: kapan harus menggunakan metode statis

Nafs
sumber
"Anda menggunakan aturan praktis untuk menggunakan metode statis dan dinamis." Apa aturan praktis ini? Dan dari siapa / apa yang Anda kutip?
barat
@weston maaf saya tidak menambahkan tautan yang saya pikirkan :). thx
nafas
0

Silakan coba:

public class ProblemFive {
    public static ProblemFive PROBLEM_FIVE = new ProblemFive();

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();
        int start = 500000000;
        int result = start;


        for (int i = start; i > 0; i--) {
            int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way
            // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
                System.out.println((System.currentTimeMillis() - startT) + " ms");
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();
        System.out.println((end - startT) + " ms");
    }

    int twentyDivCount(int a) {  // change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i < 21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }
}
chengpohi
sumber
20273 md hingga 23000+ md, berbeda untuk setiap lari
Stabbz