Eşdeğer statik ve statik olmayan yöntemlerin hızında büyük fark


86

Bu kodda, mainyöntemde bir Nesne oluşturduğumda ve sonra bu nesneler yöntemini çağırdığımda: ff.twentyDivCount(i)(16010 ms'de çalışır), bu açıklamayı kullanarak onu çağırmaktan çok daha hızlı twentyDivCount(i)çalışır : (59516 ms'de çalışır). Elbette, bir nesne oluşturmadan çalıştırdığımda, yöntemi statik hale getiriyorum, böylece ana olarak çağrılabilsin.

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

DÜZENLEME: Şimdiye kadar farklı makinelerin farklı sonuçlar ürettiği görülüyor, ancak JRE 1.8. * Kullanıldığında orijinal sonuç tutarlı bir şekilde yeniden üretiliyor gibi görünüyor.


4
Kıyaslamanızı nasıl çalıştırıyorsunuz? Bahse girerim bu, JVM'nin kodu optimize etmek için yeterli zamana sahip olmadığı bir yapıdır.
Patrick Collins

2
JVM için 's yeterli zaman derlemek ve ana yöntem için OSR gerçekleştirmek için görünüyor +PrintCompilation +PrintInlininggösterileri
Tagir Valeev

1
Kod parçacığını denedim, ancak Stabbz'ın dediği gibi böyle bir zaman farkı almıyorum. 56282ms (örnek kullanarak) 54551ms (statik yöntem olarak).
Don Chakkappan

1
@PatrickCollins Beş saniye yeterli olmalıdır. Her ikisini de ölçebilmeniz için biraz yeniden yazdım (varyant başına bir JVM başlar). Bir kıyaslama olarak hala kusurlu olduğunu biliyorum, ancak yeterince ikna edici: 1457 ms STATIC ve 5312 ms NON_STATIC.
maaartinus

1
Soruyu henüz ayrıntılı olarak araştırmadım, ancak bununla ilgili olabilir : shipilev.net/blog/2015/black-magic-method-dispatch (belki Aleksey Shipilëv bizi burada aydınlatabilir)
Marco13

Yanıtlar:


72

JRE 1.8.0_45 kullanarak benzer sonuçlar alıyorum.

Araştırma:

  1. ile java çalıştırmak -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInliningVM seçenekleriyle , her iki yöntemin de derlendiğini ve satır içi yapıldığını gösterir.
  2. Yöntemlerin kendileri için oluşturulan derlemeye bakmak önemli bir fark göstermez
  3. Bununla birlikte, satır içi hale geldiklerinde, oluşturulan montaj mainçok farklıdır, örnek yöntemi özellikle döngü açma açısından daha agresif bir şekilde optimize edilir.

Daha sonra testinizi tekrar çalıştırdım, ancak yukarıdaki şüpheyi doğrulamak için farklı döngü açma ayarlarıyla. Kodunuzu şununla çalıştırdım:

  • -XX:LoopUnrollLimit=0 ve her iki yöntem de yavaş çalışır (varsayılan seçeneklerle statik yönteme benzer).
  • -XX:LoopUnrollLimit=100 ve her iki yöntem de hızlı çalışır (varsayılan seçeneklerle örnek yöntemine benzer).

Sonuç olarak, varsayılan ayarlarla, 1.8.0_45 hotspot JIT'inin yöntem statik olduğunda döngüyü açamayacağı görülüyor (bunun neden böyle davrandığından emin olmasam da). Diğer JVM'ler farklı sonuçlar verebilir.


52 ile 71 arasında, orijinal davranış geri yüklenir (en azından benim makinemde, cevabım). Görünüşe göre statik versiyon 20 birim daha büyüktü, ama neden? Bu tuhaf.
maaartinus

3
@maaartinus Bu sayının tam olarak neyi temsil ettiğinden bile emin değilim - belge oldukça kaçamaktır: " Sunucu derleyicisi ara gösterim düğüm sayısı bu değerden daha az olan döngü gövdelerini geri al. Sunucu derleyicisi tarafından kullanılan sınır bu değerin bir işlevidir, gerçek değer değil . Varsayılan değer,
JVM'nin

Ben de bilmiyorum, ama ilk tahminim, statik yöntemlerin birim ne olursa olsun biraz daha büyük hale geldiği ve önemli olduğu noktaya geldiğimizdi. Ancak, fark oldukça büyük, bu yüzden şu anki tahminim, statik sürümün onu biraz daha büyüten bazı optimizasyonlar alması. Oluşturulan asm'a bakmadım.
maaartinus

33

Asillerin cevabına dayanan kanıtlanmamış bir tahmin.

JVM, döngü açma için 70 gibi bir eşik kullanır. Nedeni ne olursa olsun, statik çağrı biraz daha büyüktür ve açılmaz.

Sonuçları güncelle

  • İle LoopUnrollLimit52 aşağıda her iki sürüm de yavaştır.
  • 52 ile 71 arasında, yalnızca statik sürüm yavaştır.
  • 71'in üzerinde, her iki sürüm de hızlı.

Tahminimce bu tuhaf, statik çağrının dahili sunumda biraz daha büyük olması ve OP'nin garip bir duruma gelmesi. Ancak fark yaklaşık 20 gibi görünüyor ki bu hiç mantıklı değil.

 

-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

Denemek isteyenler için benim versiyonum faydalı olabilir.


Zaman '1456 ms' mi? Öyleyse, neden statik yavaş diyorsunuz?
Tony

@Tony Kafam karıştı NON_STATICve STATICfakat benim sonuç haklıydı. Şimdi düzeltildi, teşekkürler.
maaartinus

0

Bu hata ayıklama modunda yürütüldüğünde, örnek ve statik durumlar için sayılar aynıdır. Bu ayrıca, JIT'in, örnek yöntemi durumunda olduğu gibi statik durumda kodu yerel koda derlemekte tereddüt ettiği anlamına gelir.

Neden böyle yapar? Söylemesi zor; Bu daha büyük bir uygulama olsaydı muhtemelen doğru olanı yapardı ...


"Neden böyle yapıyor? Söylemesi zor, bu daha büyük bir uygulama olsaydı muhtemelen doğru olanı yapardı." Ya da gerçekten hata ayıklamak için çok büyük olan garip bir performans probleminiz olur. (Ve söylemesi o kadar da zor değil. JVM'nin asylilerin yaptığı gibi tükürdüğü montaja bakabilirsiniz.)
tmyklebu

@tmyklebu Veya gereksiz ve tamamen hata ayıklaması pahalı olan tuhaf bir performans sorunumuz var ve kolay geçici çözümler var. Sonunda, burada JIT'den bahsediyoruz, yazarları her durumda tam olarak nasıl davrandığını bilmiyorlar. :) Diğer cevaplara bir bakın, çok iyi ve konuyu açıklamaya çok yakınlar, ancak şimdiye kadar kimse tam olarak bunun neden olduğunu bilmiyor.
Dragan Bozanovic

@DraganBozanovic: Gerçek kodda gerçek sorunlara neden olduğunda "tamamen hata ayıklamak gereksiz" olmayı bırakır.
tmyklebu

0

Testi biraz değiştirdim ve şu sonuçları aldım:

Çıktı:

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

NOT

Ayrı ayrı test ederken dinamik için ~ 52 saniye ve statik için ~ 200 saniye aldım.

Program şudur:

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

Ayrıca testin sırasını şu şekilde değiştirdim:

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

Ve bunu anladım:

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

Gördüğünüz gibi, dinamik statikten önce çağrılırsa, statik için hız önemli ölçüde azalır.

Bu karşılaştırmaya göre:

Ben varsayımında tüm JVM optimizasyonu bağlı olduğunu. bu nedenle, statik ve dinamik yöntemlerin kullanımı için genel kurallara uymanızı tavsiye ederim.

THUMB KURAL:

Java: statik yöntemler ne zaman kullanılır?


"statik ve dinamik yöntemlerin kullanımı için genel kurallara uymalısınız." Bu pratik kural nedir? Ve kimden / neyden alıntı yapıyorsunuz?
weston

@weston üzgünüm düşündüğüm bağlantıyı eklemedim :). thx
nafas

0

Deneyin lütfen:

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

20273 ms - 23000+ ms, her çalıştırma için farklı
Stabbz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.