Java statik çağrıları, statik olmayan çağrılardan daha mı yoksa daha mı ucuzdur?


Yanıtlar:


73

İlk olarak: Performans temelinde statik ve statik olmayan arasında seçim yapmamalısınız.

İkincisi: pratikte herhangi bir fark yaratmayacak. Hotspot, bir yöntem için statik çağrıları daha hızlı, statik olmayan çağrıları ise başka bir yöntem için daha hızlı hale getirecek şekilde optimize etmeyi seçebilir.

Üçüncüsü: Statik ve statik olmayanı çevreleyen efsanelerin çoğu ya çok eski JVM'lere (Hotspot'un yaptığı optimizasyonun yakınında hiçbir yerde bulunmadı) ya da C ++ hakkında bazı hatırlanan bilgiler (dinamik bir çağrının bir bellek erişimi daha kullandığı statik bir çağrıdan daha fazla).


1
Kesinlikle haklısın, sadece buna dayalı statik yöntemleri tercih etmemelisin. Bununla birlikte, statik yöntemlerin tasarıma iyi uyması durumunda, bunların örnek yöntemlerden daha hızlı olmasa da en az hızlı olduklarını ve performans temelinde göz ardı edilmemesi gerektiğini bilmek yararlıdır.
Will

2
@AaronDigulla -.- buraya şu anda optimizasyon yaptığım için geldiğimi söylersem, erken değil, ama gerçekten ihtiyacım olduğunda? OP'nin vaktinden önce optimize etmek istediğini varsaydınız, ancak bu sitenin biraz küresel olduğunu biliyorsunuz ... Değil mi? Kaba olmak istemiyorum ama lütfen bir dahaki sefere böyle şeyler varsaymayın.
Dalibor Filus

1
@DaliborFilus Bir denge bulmam gerekiyor. Statik yöntemler kullanmak her türlü soruna neden olur, bu nedenle özellikle ne yaptığınızı bilmediğinizde kaçınılmalıdır. İkinci olarak, çoğu "yavaş" kod (kötü) tasarımdan kaynaklanır, Seçim Dilinin yavaş olması değil. Kodunuz yavaşsa, statik yöntemler, kesinlikle hiçbir şey yapmayan çağırma yöntemleri olmadıkça muhtemelen onu kaydetmeyecektir . Çoğu durumda, kod içinde yöntemlere çağrı yükü cüce olacak.
Aaron Digulla

7
Olumsuz oy verildi. Bu soruya cevap vermiyor. Performans faydaları hakkında sorulan soru. Tasarım ilkeleri hakkında görüş sormadı.
Colm Bhandal

4
Bir papağanı "erken opimizasyon tüm kötülüklerin köküdür" diyecek şekilde eğitirsem, performansı papağan kadar iyi bilen insanlardan 1000 oy alırdım.
rghome

62

Dört yıl sonra...

Tamam, bu soruyu sonsuza kadar çözme umuduyla, farklı arama türlerinin (sanal, sanal olmayan, statik) birbirleriyle nasıl karşılaştırıldığını gösteren bir kıyaslama yazdım.

Bunu ideone üzerinde çalıştırdım ve elimde bu:

(Daha fazla sayıda yineleme daha iyidir.)

    Success time: 3.12 memory: 320576 signal:0
  Name          |  Iterations
    VirtualTest |  128009996
 NonVirtualTest |  301765679
     StaticTest |  352298601
Done.

Beklendiği gibi, sanal yöntem çağrıları en yavaş olanıdır, sanal olmayan yöntem çağrıları daha hızlıdır ve statik yöntem çağrıları daha da hızlıdır.

Beklemediğim şey, farklılıkların bu kadar belirgin olmasıydı: Sanal yöntem çağrıları, sanal olmayan yöntem çağrılarının yarısından daha az hızda çalışacak şekilde ölçüldü, bu da statik çağrılardan % 15 daha yavaş çalışacak şekilde ölçüldü . Bu ölçümler bunu gösteriyor; Her sanal, sanal olmayan ve statik yöntem çağrısı için, kıyaslama kodumun bir tamsayı değişkenini artırma, bir boole değişkenini kontrol etme ve doğru değilse döngü gibi ek bir sabit ek yükü olduğundan, gerçek farklılıklar aslında biraz daha belirgin olmalıdır.

Sanırım sonuçlar CPU'dan CPU'ya ve JVM'den JVM'ye değişecek, bu yüzden bir deneyin ve ne elde ettiğinizi görün:

import java.io.*;

class StaticVsInstanceBenchmark
{
    public static void main( String[] args ) throws Exception
    {
        StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark();
        program.run();
    }

    static final int DURATION = 1000;

    public void run() throws Exception
    {
        doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ), 
                     new NonVirtualTest( new ClassWithNonVirtualMethod() ), 
                     new StaticTest() );
    }

    void doBenchmark( Test... tests ) throws Exception
    {
        System.out.println( "  Name          |  Iterations" );
        doBenchmark2( devNull, 1, tests ); //warmup
        doBenchmark2( System.out, DURATION, tests );
        System.out.println( "Done." );
    }

    void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception
    {
        for( Test test : tests )
        {
            long iterations = runTest( duration, test );
            printStream.printf( "%15s | %10d\n", test.getClass().getSimpleName(), iterations );
        }
    }

    long runTest( int duration, Test test ) throws Exception
    {
        test.terminate = false;
        test.count = 0;
        Thread thread = new Thread( test );
        thread.start();
        Thread.sleep( duration );
        test.terminate = true;
        thread.join();
        return test.count;
    }

    static abstract class Test implements Runnable
    {
        boolean terminate = false;
        long count = 0;
    }

    static class ClassWithStaticStuff
    {
        static int staticDummy;
        static void staticMethod() { staticDummy++; }
    }

    static class StaticTest extends Test
    {
        @Override
        public void run()
        {
            for( count = 0;  !terminate;  count++ )
            {
                ClassWithStaticStuff.staticMethod();
            }
        }
    }

    static class ClassWithVirtualMethod implements Runnable
    {
        int instanceDummy;
        @Override public void run() { instanceDummy++; }
    }

    static class VirtualTest extends Test
    {
        final Runnable runnable;

        VirtualTest( Runnable runnable )
        {
            this.runnable = runnable;
        }

        @Override
        public void run()
        {
            for( count = 0;  !terminate;  count++ )
            {
                runnable.run();
            }
        }
    }

    static class ClassWithNonVirtualMethod
    {
        int instanceDummy;
        final void nonVirtualMethod() { instanceDummy++; }
    }

    static class NonVirtualTest extends Test
    {
        final ClassWithNonVirtualMethod objectWithNonVirtualMethod;

        NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod )
        {
            this.objectWithNonVirtualMethod = objectWithNonVirtualMethod;
        }

        @Override
        public void run()
        {
            for( count = 0;  !terminate;  count++ )
            {
                objectWithNonVirtualMethod.nonVirtualMethod();
            }
        }
    }

    static final PrintStream devNull = new PrintStream( new OutputStream() 
    {
        public void write(int b) {}
    } );
}

Bu performans farkının yalnızca parametresiz yöntemleri çağırmaktan başka hiçbir şey yapmayan koda uygulanabilir olduğuna dikkat etmek önemlidir. Çağrılar arasında sahip olduğunuz diğer kod ne olursa olsun, farklılıkları azaltacaktır ve buna parametre geçişi de dahildir. Aslında, durağan ve sanal olmayan çağrılar arasındaki% 15'lik fark, muhtemelen işaretçinin statik yönteme geçirilmesine gerek olmadığı gerçeğiyle tam olarak açıklanmaktadır this. Dolayısıyla, farklı çağrı türleri arasındaki farkın hiçbir net etkiye sahip olmayacak şekilde seyreltilmesi için çağrılar arasında önemsiz şeyler yapan oldukça az miktarda kod gerekir.

Ayrıca, sanal yöntem çağrılarının bir nedeni vardır; hizmet verecek bir amacı vardır ve temel donanım tarafından sağlanan en verimli araçlar kullanılarak uygulanırlar. (CPU komut seti.) Bunları sanal olmayan veya statik çağrılarla değiştirerek ortadan kaldırmak istiyorsanız, işlevselliklerini taklit etmek için bir miktar fazladan kod eklemek zorunda kalırsanız, sonuçta ortaya çıkan net ek yükünüz bağlanır. daha az değil, daha çok olmak. Büyük olasılıkla, çok, çok, anlaşılmaz biçimde çok, daha fazlası.


7
'Sanal' bir C ++ terimidir. Java'da sanal yöntem yoktur. Çalışma zamanı-polimorfik olan sıradan yöntemler ve olmayan statik veya son yöntemler vardır.
Zhenya

18
@levgen evet, bakış açısı dilin resmi üst düzey genel görünümü kadar dar olan biri için, tam olarak söylediğiniz gibi. Ancak elbette üst düzey kavramlar, java ortaya çıkmadan çok önce icat edilmiş, iyi kurulmuş düşük düzeyli mekanizmalar kullanılarak uygulanmaktadır ve sanal yöntemler bunlardan biridir. Kaputun altına sadece küçük bir bakış atarsanız, bunun böyle olduğunu hemen göreceksiniz: docs.oracle.com/javase/specs/jvms/se7/html/…
Mike

13
Erken optimizasyon hakkında varsayımda bulunmadan soruyu yanıtladığınız için teşekkür ederiz. Mükemmel cevap.
vegemite4me

3
Evet, tam olarak bunu kastettim. Her neyse, testi makinemde yaptım. Böyle bir kıyaslama için bekleyebileceğiniz titremenin yanı sıra hızda hiçbir fark yoktur: VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198OpenJDK kurulumumda. FTR: finalDeğiştiriciyi kaldırırsam bu bile doğru . Btw. terminateSahaya gitmek zorundaydım volatile, aksi halde test bitmedi.
Marten

4
Bilginize, ben Android 6 çalıştıran Nexus 5 ziyade şaşırtıcı sonuçlar elde: VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170. Sadece dizüstü bilgisayarımdaki OpenJDK, 40 kat daha fazla yineleme gerçekleştirmeyi başarmakla kalmıyor, statik test her zaman yaklaşık% 30 daha az iş hacmine sahip. Bu, SANAT'a özgü bir fenomen olabilir, çünkü Android 4.4 tablette beklenen sonucu alıyorum:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Marten

45

Eh, statik aramalar olamaz (bu nedenle her zaman inlining aday olan) ve herhangi bir hükümsüzlük kontrolleri gerekmez geçersiz kılınan olun. HotSpot, bu avantajları tamamen ortadan kaldırabilecek yöntemler gibi bir dizi harika optimizasyon yapar, ancak bunlar statik bir çağrının daha hızlı olmasının olası nedenleri.

Bununla birlikte, bu tasarımınızı etkilememelidir - en okunaklı, doğal şekilde kodlayın - ve bu tür bir mikro optimizasyon için yalnızca haklı bir sebebiniz varsa endişelenmelisiniz (ki bunu neredeyse asla yapmayacaksınız).


Bunlar, statik bir aramanın daha hızlı olmasının olası nedenleri olabilir. Bana bu nedenleri açıklayabilir misiniz?
JavaTechnical

6
@JavaTechnical: Cevap bu nedenleri açıklıyor - geçersiz kılma yok (bu, uygulamayı her seferinde kullanmak için çalışmanıza gerek olmadığı ve satır içi yapabileceğiniz anlamına gelir ) ve yöntemi bir boş başvuru.
Jon Skeet

6
@JavaTechnical: Anlamıyorum. Size bir satır içi fırsatla birlikte statik yöntemler için hesaplanması / kontrol edilmesi gerekmeyen şeyler verdim. İşi değil ise bir performans yararı. Anlayacak ne kaldı?
Jon Skeet

Statik değişkenler, statik olmayan değişkenlerden daha hızlı mı alınır?
JavaTechnical

1
@JavaTechnical: Gerçekleştirilecek geçersizlik denetimi yok - ancak JIT derleyicisi bu denetimi kaldırabilirse (içeriğe özgü olacaktır), çok fazla fark beklemem. Belleğin önbellekte olup olmadığı gibi şeyler çok daha önemli olacaktır.
Jon Skeet

18

Derleyiciye / VM'ye özgüdür.

  • Teoride , statik bir çağrı biraz daha verimli hale getirilebilir çünkü sanal bir işlev araması yapmasına gerek yoktur ve ayrıca gizli "bu" parametresinin ek yükünü de önleyebilir.
  • Pratikte , birçok derleyici bunu yine de optimize edecektir.

Bu nedenle, bunu uygulamanızda gerçekten kritik bir performans sorunu olarak tanımlamadıysanız, muhtemelen rahatsız etmeye değmez. Erken optimizasyon, tüm kötülüklerin vb. Köküdür ...

Ancak var bu optimizasyon aşağıdaki durumda önemli bir performans artışı elde görülme:

  • Bellek erişimi olmayan çok basit bir matematiksel hesaplama gerçekleştiren yöntem
  • Sıkı bir iç döngüde saniyede milyonlarca kez çalıştırılan yöntem
  • Her bit performansın önemli olduğu CPU'ya bağlı uygulama

Yukarıdakiler sizin için geçerliyse, test etmeye değer olabilir.

Statik bir yöntemi kullanmak için başka bir iyi (ve potansiyel olarak daha da önemli!) Neden daha vardır - eğer yöntemin aslında statik semantiği varsa (yani mantıksal olarak sınıfın belirli bir örneğine bağlı değilse), o zaman onu statik yapmak mantıklıdır. bu gerçeği yansıtmak için. Deneyimli Java programcıları daha sonra statik değiştiriciyi fark edecek ve hemen "aha! Bu yöntemin statik olduğunu, böylece bir örneğe ihtiyaç duymayacağını ve muhtemelen örneğe özgü durumu değiştirmediğini" düşüneceklerdir. Böylece yöntemin statik doğasını etkili bir şekilde iletmiş olacaksınız ...


13

Statik ve statik olmayan çağrıların performansındaki herhangi bir farkın uygulamanızda bir fark yaratması inanılmaz derecede olası değildir. "Erken optimizasyonun tüm kötülüklerin kökü olduğunu" unutmayın.



"Erken optimizasyon, tüm kötülüklerin köküdür".
user2121

Soru, "Öyle ya da böyle bir performans faydası var mı?" İdi ve bu tam olarak bu soruyu yanıtlıyor.
DJClayworth

13

Önceki posterlerin dediği gibi: Bu, erken bir optimizasyona benziyor.

Bununla birlikte, bir fark vardır (statik olmayan çağrıların işlemsel yığına bir aranan nesnenin ek bir itilmesini gerektirmesi gerçeğinin bir kısmı):

Statik yöntemler geçersiz kılınamadığından, çalışma zamanında statik yöntem çağrısı için herhangi bir sanal arama olmayacaktır . Bu, bazı durumlarda gözlemlenebilir bir farka neden olabilir.

Bayt kod düzeyinde fark olmayan statik yöntem çağrısı aracılığıyla yapılır olmasıdır INVOKEVIRTUAL, INVOKEINTERFACEya INVOKESPECIALstatik bir yöntem çağrısı aracılığıyla yapılır iken INVOKESTATIC.


2
Bununla birlikte, özel bir örnek yöntemi (en azından tipik olarak) invokespecialsanal olmadığı için kullanılarak çağrılır .
Mark Peters

Ah, ilginç, sadece kurucuları düşünebiliyordum, bu yüzden onu atladım! Teşekkürler! (yanıt güncelleniyor)
aioobe

2
JVM, somutlaştırılmış bir tür varsa optimize edecektir. B, A'yı genişletirse ve B'nin hiçbir örneği somutlaştırılmadıysa, A'daki yöntem çağrıları sanal bir tablo aramasına ihtiyaç duymaz.
Steve Kuo

13

7 yıl sonra ...

Mike Nakis'in bulduğu sonuçlara çok fazla güvenmiyorum çünkü bunlar Hotspot optimizasyonları ile ilgili bazı genel sorunları ele almıyorlar. JMH kullanarak karşılaştırmalar yaptım ve bir örnek yönteminin ek yükünün makinemde statik bir çağrıya kıyasla yaklaşık% 0,75 olduğunu buldum. Düşük ek yük göz önüne alındığında, gecikmeye duyarlı operasyonların çoğu dışında, muhtemelen bir uygulama tasarımındaki en büyük endişe olmadığını düşünüyorum. JMH benchmarkımın özet sonuçları aşağıdaki gibidir;

java -jar target/benchmark.jar

# -- snip --

Benchmark                        Mode  Cnt          Score         Error  Units
MyBenchmark.testInstanceMethod  thrpt  200  414036562.933 ± 2198178.163  ops/s
MyBenchmark.testStaticMethod    thrpt  200  417194553.496 ± 1055872.594  ops/s

Koda buradan Github'da bakabilirsiniz;

https://github.com/nfisher/svsi

Kriterin kendisi oldukça basittir, ancak ölü kodu ortadan kaldırmayı ve sürekli katlamayı en aza indirmeyi amaçlamaktadır. Muhtemelen kaçırdığım / gözden kaçırdığım başka optimizasyonlar da var ve bu sonuçların JVM sürümüne ve işletim sistemine göre değişiklik göstermesi muhtemel.

package ca.junctionbox.svsi;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;

class InstanceSum {
    public int sum(final int a, final int b) {
        return a + b;
    }
}

class StaticSum {
    public static int sum(final int a, final int b) {
        return a + b;
    }
}

public class MyBenchmark {
    private static final InstanceSum impl = new InstanceSum();

    @State(Scope.Thread)
    public static class Input {
        public int a = 1;
        public int b = 2;
    }

    @Benchmark
    public void testStaticMethod(Input i, Blackhole blackhole) {
        int sum = StaticSum.sum(i.a, i.b);
        blackhole.consume(sum);
    }

    @Benchmark
    public void testInstanceMethod(Input i, Blackhole blackhole) {
        int sum = impl.sum(i.a, i.b);
        blackhole.consume(sum);
    }
}

1
Tamamen akademik ilgi burada. Bu tür bir mikro optimizasyonun, ops/sbirincil olarak bir ART ortamı dışındaki ölçütler üzerinde sahip olabileceği olası faydaları merak ediyorum (örneğin, bellek kullanımı, azaltılmış .oat dosya boyutu vb.). Bu diğer ölçümleri karşılaştırmak için nispeten basit araçlar / yollar biliyor musunuz?
Ryan Thomas

Hotspot, sınıf yolunda InstanceSum için herhangi bir uzantı olmadığını anlar. InstanceSum'u genişleten ve yöntemi geçersiz kılan başka bir sınıf eklemeyi deneyin.
milan

12

Bir yöntemin statik olup olmayacağına karar vermek için performans yönü ilgisiz olmalıdır. Bir performans sorununuz varsa, birçok yöntemi statik yapmak günü kurtarmayacaktır. Bununla birlikte, statik yöntemler neredeyse kesinlikle herhangi bir örnek yöntemden daha yavaş değildir , çoğu durumda marjinal olarak daha hızlıdır :

1.) Statik yöntemler polimorfik değildir, bu nedenle JVM'nin yürütülecek gerçek kodu bulmak için daha az karar vermesi gerekir. Hotspot, yalnızca bir uygulama sitesi olan örnek yöntemi çağrılarını optimize edeceğinden, Hotspot Age of Hotspot'ta tartışmalı bir noktadır.

2.) Bir başka ince fark, statik yöntemlerin açıkça "bu" referansa sahip olmamasıdır. Bu, aynı imzaya ve gövdeye sahip bir örnek yönteminden bir daha küçük bir yığın çerçevesi ile sonuçlanır ("bu", bayt kodu seviyesindeki yerel değişkenlerin 0 numaralı yuvasına yerleştirilirken, statik yöntemler için yuva 0, ilk yöntemin parametresi).


5

Bir fark olabilir ve herhangi bir kod parçası için her iki yönde de olabilir ve JVM'nin küçük bir sürümüyle bile değişebilir.

Bu, kesinlikle unutmanız gereken küçük verimliliklerin% 97'sinin bir parçasıdır .


2
Yanlış. Hiçbir şey varsayamazsınız. Bir ön uç kullanıcı arayüzü için gerekli olan sıkı bir döngü olabilir ve bu, kullanıcı arayüzünün ne kadar "hızlı" olduğu konusunda büyük bir fark yaratabilir. Örneğin, TableViewmilyonlarca kayıt için bir arama .
üçleme

0

Teorik olarak daha ucuz.

Statik başlatma, nesnenin bir örneğini oluştursanız bile yapılacaktır, oysa statik yöntemler normalde bir yapıcıda yapılan herhangi bir başlatma yapmaz.

Ancak bunu test etmedim.


1
@R. Bemrose, statik ilklendirmenin bu soruyla ne ilgisi var?
Kirk Woll

@Kirk Woll: Çünkü Statik Başlatma, sınıfa ilk başvurulduğunda yapılır ... ilk statik yöntem çağrısından önce dahil.
Powerlord

@R. Bemrose, tabi ki, başlamak için sınıfı sanal makineye yüklüyor. Sıralayıcı olmayan bir IMO gibi görünüyor.
Kirk Woll

0

Jon'un belirttiği gibi, statik yöntemler geçersiz kılınamaz, bu nedenle basitçe statik bir yöntemi çağırmak - yeterince saf bir Java çalışma zamanında - bir örnek yöntemini çağırmaktan daha hızlı olabilir .

Ama sonra, birkaç nanosaniye tasarruf etmek için tasarımınızı karıştırmayı önemsediğiniz noktada olduğunuzu varsayarsak bile, bu sadece başka bir soruyu gündeme getiriyor: kendinizi geçersiz kılacak yönteme ihtiyacınız olacak mı? Burada ve orada bir nanosaniye tasarruf etmek için bir örnek yöntemini statik bir yönteme dönüştürmek için kodunuzu değiştirirseniz ve ardından geri dönüp bunun üzerine kendi dağıtım programınızı uygularsanız, sizinki neredeyse kesinlikle oluşturulandan daha az verimli olacaktır. zaten Java çalışma zamanınıza.


-2

Buradaki diğer harika yanıtlara bunun sizin akışınıza da bağlı olduğunu eklemek isterim, örneğin:

Public class MyDao {

   private String sql = "select * from MY_ITEM";

   public List<MyItem> getAllItems() {
       springJdbcTemplate.query(sql, new MyRowMapper());
   };
};

Her arama için yeni bir MyRowMapper nesnesi oluşturduğunuza dikkat edin.
Bunun yerine burada statik bir alan kullanmanızı öneririm.

Public class MyDao {

   private static RowMapper myRowMapper = new MyRowMapper();
   private String sql = "select * from MY_ITEM";

   public List<MyItem> getAllItems() {
       springJdbcTemplate.query(sql, myRowMapper);
   };
};
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.