Java Yansıma Performansı


Yanıtlar:


169

Evet kesinlikle. Yansıma yoluyla bir sınıf aranıyor olduğu büyüklükle , daha pahalı.

Java'nın yansıma ile ilgili belgelerinden alıntı :

Yansıma, dinamik olarak çözümlenen türleri içerdiğinden, bazı Java sanal makine optimizasyonları gerçekleştirilemez. Sonuç olarak, yansıtıcı işlemler yansıtıcı olmayan muadillerine göre daha yavaş performansa sahiptir ve performansa duyarlı uygulamalarda sıkça çağrılan kod bölümlerinden kaçınılmalıdır.

Sun JRE 6u10'u çalıştırarak makinemde 5 dakika içinde hacklediğim basit bir test:

public class Main {

    public static void main(String[] args) throws Exception
    {
        doRegular();
        doReflection();
    }

    public static void doRegular() throws Exception
    {
        long start = System.currentTimeMillis();
        for (int i=0; i<1000000; i++)
        {
            A a = new A();
            a.doSomeThing();
        }
        System.out.println(System.currentTimeMillis() - start);
    }

    public static void doReflection() throws Exception
    {
        long start = System.currentTimeMillis();
        for (int i=0; i<1000000; i++)
        {
            A a = (A) Class.forName("misc.A").newInstance();
            a.doSomeThing();
        }
        System.out.println(System.currentTimeMillis() - start);
    }
}

Bu sonuçlarla:

35 // no reflection
465 // using reflection

Arama ve örneklemenin birlikte yapıldığını unutmayın ve bazı durumlarda arama yeniden düzenlenebilir, ancak bu sadece temel bir örnektir.

Sadece başlasanız bile, yine de bir performans puanı alırsınız:

30 // no reflection
47 // reflection using one lookup, only instantiating

Yine YMMV.


5
Makinemde sadece bir Class.forName () çağrısı ile .newInstance () çağrısı 30 ya da daha fazla puan alır. VM sürümüne bağlı olarak, fark uygun bir önbellek stratejisiyle düşündüğünüzden daha yakın olabilir.
Sean Reilly

56
@Peter Lawrey aşağıdaki derleyicinin yansıtıcı olmayan çözümü optimize ettiği için bu testin tamamen geçersiz olduğuna dikkat çekti (Hiçbir şeyin yapılmadığını kanıtlayabilir ve for döngüsünü optimize edebilir). Yeniden çalışılması gerekiyor ve muhtemelen SO / kötü / yanıltıcı bilgi olarak kaldırılmalıdır. Optimize edicinin onu optimize etmesini önlemek için her iki durumda da bir dizi içinde oluşturulan nesneleri önbelleğe alın. (Bunu yansıtıcı durumda yapamaz çünkü kurucunun yan etkileri olmadığını kanıtlayamaz)
Bill K

6
@ Fatura K - taşınmayalım. Evet, optimizasyon nedeniyle sayılar kapalı. Hayır, test tamamen geçersiz değil . Sonucu çarpma olasılığını ortadan kaldıran bir çağrı ekledim ve sayılar hala yansımaya karşı yığılmış. Her halükarda, bunun çok kaba bir mikro-ölçüt olduğunu unutmayın, bu sadece yansımanın her zaman belirli bir yüke maruz kaldığını gösterir
Yuval Adam

4
Bu muhtemelen işe yaramaz bir ölçüttür. Bir şeylerin ne yaptığına bağlı olarak. Görünür yan etki ile hiçbir şey yapmazsa, kıyaslamanız sadece ölü kod çalıştırır.
nes1983

9
JVM'nin 35 kat yansıma optimizasyonuna tanık oldum. Testi bir döngüde tekrar tekrar çalıştırmak, optimize edilmiş kodu nasıl test ettiğinizdir. Birinci yineleme: 3045ms, ikinci yineleme: 2941ms, üçüncü yineleme: 90ms, dördüncü yineleme: 83ms. Kodu: c.newInstance (i). c bir Yapıcıdır. Yansıtıcı olmayan kod: 13, 4, 3 .. ms kez veren yeni A (i). Yani evet, bu durumda yansıma yavaştı, ancak insanların sonuçlandırdığı kadar yavaş değil, çünkü gördüğüm her test, JVM'ye bayt kodlarını makine ile değiştirme fırsatı vermeden testi bir kez çalıştırıyorlar kodu.
Mike

87

Evet, daha yavaş.

Ama lanet olsun 1 kuralını hatırlayın - PREMATURE OPTIMIZATION TÜM KÖTÜLÜĞÜN KÖKÜDÜR

(DRY için # 1 ile bağlanabilir)

Yemin ederim, eğer biri bana işyerinde geldiyse ve bana bunu sorarsa, önümüzdeki birkaç ay boyunca kodları konusunda çok dikkatli olacağım.

İhtiyacınız olduğundan emin olana kadar asla optimizasyon yapmamalısınız, o zamana kadar iyi, okunabilir bir kod yazın.

Oh, ve ben de aptal kod yazmak demek istemiyorum. Sadece yapabileceğiniz en temiz yolu düşünün - kopyala ve yapıştır yok, vb. , "kötü" programlama)

Böyle sorular duyduğumda beni korkutuyor, ama sonra herkesin tüm kuralları kendileri öğrenmeden öğrenmesi gerektiğini unutuyorum. Bir kişinin "Optimize Edilmiş" bir şeyi hata ayıklamak için harcadıktan sonra bunu alacaksınız.

DÜZENLE:

Bu konuda ilginç bir şey oldu. 1 numaralı cevabı kontrol edin, derleyicinin bir şeyleri optimize etmede ne kadar güçlü olduğuna bir örnek. Yansıtıcı olmayan örnekleme tamamen etkisiz hale getirilebildiğinden test tamamen geçersizdir.

Ders? Temiz, düzgün kodlanmış bir çözüm yazıp çok yavaş olduğunu kanıtlayana kadar HİÇBİR optimizasyon yapmayın.


28
Bu yanıtın duygusuna tamamen katılıyorum, ancak büyük bir tasarım kararına başlamak üzereyseniz, performans hakkında bir fikre sahip olmanıza yardımcı olur, böylece tamamen işe yaramaz bir yola çıkmazsınız. Belki de gayret göstermekte?
Limbik Sistem

26
-1: Bir şeyleri yanlış yapmaktan kaçınmak optimizasyon değil, sadece bir şeyler yapmaktır. Optimizasyon, gerçek veya hayali performans endişeleri nedeniyle işleri yanlış, karmaşık bir şekilde yapıyor.
soru

5
@soru tamamen katılıyorum. Ekleme sıralaması için bir dizi listesi üzerinde bağlantılı bir liste seçmek, işleri yapmanın doğru yoludur. Ancak bu özel soru - orijinal sorunun her iki tarafı için iyi kullanım durumları vardır, bu nedenle en kullanışlı çözüm yerine performansa dayalı bir tane seçmek yanlış olur. Hiç katılmıyorum emin değilim, bu yüzden neden "-1" dediğini bilmiyorum.
Bill K

14
Herhangi bir mantıklı analist programcısının verimliliği erken bir aşamada düşünmesi gerekir, aksi takdirde verimli ve maliyetli bir zaman diliminde optimize edilemeyen bir sistem elde edebilirsiniz. Hayır, her saat döngüsünü optimize etmiyorsunuz ama kesinlikle sınıf örnekleme kadar temel bir şey için en iyi uygulamaları kullanıyorsunuz. Bu örnek, Niçin yansımayla ilgili bu tür soruları düşündüğünüzün harika bir örneğidir. Devam eden ve bir milyon hat sistemi boyunca sadece daha sonra büyüklük derecelerinin çok yavaş olduğunu keşfetmek için yansıma kullanan oldukça fakir bir programcı olurdu.
RichieHH

2
@Richard Riley Genellikle sınıf örneği, üzerinde düşüneceğiniz seçili sınıflar için oldukça nadir bir olaydır. Sanırım haklısın - bazı insanlar sürekli olarak yeniden yaratılanlar bile her sınıfı yansıtıcı bir şekilde başlatabilir. (Olduktan sonra bile yeniden kullanmak için sınıf örneklerinin bir önbelleğini uygulamak ve kodunuza çok fazla zarar vermemek olsa da oldukça kötü programlama diyebilirim - bu yüzden hala okunabilirlik için her zaman tasarım söyleyebilirim, sonra profil ve optimize edin sonrası)
Bill K

36

A a = yeni A () 'nın JVM tarafından optimize edildiğini görebilirsiniz. Nesneleri bir diziye koyarsanız, çok iyi performans göstermezler. ;) Aşağıdaki baskılar ...

new A(), 141 ns
A.class.newInstance(), 266 ns
new A(), 103 ns
A.class.newInstance(), 261 ns

public class Run {
    private static final int RUNS = 3000000;

    public static class A {
    }

    public static void main(String[] args) throws Exception {
        doRegular();
        doReflection();
        doRegular();
        doReflection();
    }

    public static void doRegular() throws Exception {
        A[] as = new A[RUNS];
        long start = System.nanoTime();
        for (int i = 0; i < RUNS; i++) {
            as[i] = new A();
        }
        System.out.printf("new A(), %,d ns%n", (System.nanoTime() - start)/RUNS);
    }

    public static void doReflection() throws Exception {
        A[] as = new A[RUNS];
        long start = System.nanoTime();
        for (int i = 0; i < RUNS; i++) {
            as[i] = A.class.newInstance();
        }
        System.out.printf("A.class.newInstance(), %,d ns%n", (System.nanoTime() - start)/RUNS);
    }
}

Bu, makinemdeki farkın yaklaşık 150 ns olduğunu gösteriyor.


optimize ediciyi yeni öldürdünüz, şimdi her iki versiyon da yavaş. Bu nedenle yansıma hala yavaştır.
gbjbaanb

13
@ gbjbaanb optimize edici oluşturma işlemini optimize ederse geçerli bir test değildi. @ Peter'ın testi bu nedenle geçerlidir çünkü oluşturma sürelerini karşılaştırır (Optimizer herhangi bir gerçek dünya durumunda çalışamaz çünkü gerçek dünyadaki herhangi bir durumda örneklediğiniz nesnelere ihtiyacınız vardır).
Bill K

10
@ nes1983 Bu durumda daha iyi bir kriter oluşturma fırsatını yakalamış olabilirsiniz. Belki de yöntemin gövdesinde olması gereken gibi yapıcı bir şey sunabilirsiniz.
Peter Lawrey

1
Mac'imde openjdk 7u4, fark 100ns ile 100ns arasında. A'yı dizide saklamak yerine hashCodes'u depolarım. -Verbose: class derseniz, sıcak noktanın A'yı ve beraberindeki hızlandırmayı oluşturmak için bayt kodu oluşturduğunu görebilirsiniz.
Ron

@PeterLawrey Bir kez (bir çağrı Class.getDeclaredMethod) arar ve sonra Method.invokebirden çok kez ararsam ? Yansımayı bir veya daha fazla çağırdığımda mı kullanıyorum? Takip eden soru, bunun yerine Methodbir ise Constructorve Constructor.newInstancebirden çok kez yaparsam ?
tmj

28

Yansımadan daha hızlı bir şeye gerçekten ihtiyaç varsa ve bu sadece erken bir optimizasyon değilse, ASM veya daha yüksek bir kütüphane ile bayt kodu oluşturma bir seçenektir. Bayt kodunu ilk kez oluşturmak yalnızca yansıma kullanmaktan daha yavaştır, ancak bayt kodu oluşturulduktan sonra normal Java kodu kadar hızlıdır ve JIT derleyicisi tarafından optimize edilir.

Kod oluşturma kullanan uygulamalara bazı örnekler:

  • Tarafından üretilen vekiller üzerinde yöntemleri çağırma CGLIB biraz daha hızlı Java'nın daha dinamik proxy CGLIB onun vekiller için bytecode üretir, çünkü ancak dinamik yakınlık sadece bir yansıma (kullanmak ben ölçülen daha hızlı bir yöntem çağrılarında 10x hakkında olmak CGLIB ancak oluşturarak vekiller yavaştı).

  • JSerial , yansıma kullanmak yerine serileştirilmiş nesnelerin alanlarını okumak / yazmak için bayt kodu oluşturur. Orada bazı kriterler JSerial sitesinde.

  • % 100 emin değilim (ve şimdi kaynağı okumak istemiyorum), ama Guice bağımlılık enjeksiyonu yapmak için bayt kodu oluşturduğunu düşünüyorum . Yanlışsam düzelt.


27

"Anlamlı" tamamen bağlama bağlıdır.

Bazı yapılandırma dosyalarına dayalı olarak tek bir işleyici nesnesi oluşturmak için yansıma kullanıyorsanız ve daha sonra zamanınızı veritabanı sorgularını çalıştırarak harcıyorsanız, bu önemsizdir. Sıkı bir döngüde yansıma yoluyla çok sayıda nesne oluşturuyorsanız, evet, bu önemlidir.

Genel olarak, tasarım esnekliği (gerektiğinde!) Performans değil yansıma kullanımınızı yönlendirmelidir. Ancak, performansın bir sorun olup olmadığını belirlemek için bir tartışma forumundan rastgele yanıtlar almak yerine profil oluşturmanız gerekir.


24

Yansıması olan bir ek yük var, ancak modern VM'lerde eskisinden çok daha küçük.

Programınızdaki her basit nesneyi oluşturmak için yansıma kullanıyorsanız, bir şeyler yanlıştır. Ara sıra kullanmak, iyi bir nedeniniz olduğunda, hiç sorun olmamalı.


11

Evet, Reflection kullanırken bir performans isabeti var, ancak optimizasyon için olası bir çözüm yöntemi önbelleğe almaktır:

  Method md = null;     // Call while looking up the method at each iteration.
      millis = System.currentTimeMillis( );
      for (idx = 0; idx < CALL_AMOUNT; idx++) {
        md = ri.getClass( ).getMethod("getValue", null);
        md.invoke(ri, null);
      }

      System.out.println("Calling method " + CALL_AMOUNT+ " times reflexively with lookup took " + (System.currentTimeMillis( ) - millis) + " millis");



      // Call using a cache of the method.

      md = ri.getClass( ).getMethod("getValue", null);
      millis = System.currentTimeMillis( );
      for (idx = 0; idx < CALL_AMOUNT; idx++) {
        md.invoke(ri, null);
      }
      System.out.println("Calling method " + CALL_AMOUNT + " times reflexively with cache took " + (System.currentTimeMillis( ) - millis) + " millis");

sonuçlanacak:

[java] Arama yöntemi 1000000 kez arama ile refleks olarak 5618 milis aldı

[java] Çağırma yöntemi önbellek ile 1000000 kez 270 milis aldı


Yeniden kullanma yöntemi / kurucu gerçekten yararlıdır ve yardımcı olur, ancak yukarıdaki testin normal kıyaslama sorunları nedeniyle anlamlı sayılar vermediğini unutmayın (ısınma yok, bu nedenle özellikle ilk döngü çoğunlukla JVM / JIT ısınma süresini ölçüyor).
StaxMan

7

Yansıma yavaştır, ancak nesne tahsisi yansımanın diğer yönleri kadar umutsuz değildir. Yansıma tabanlı örnekleme ile eşdeğer performans elde etmek için kodunuzu yazmanız gerekir, böylece jit hangi sınıfın örneklendiğini söyleyebilir. Sınıfın kimliği belirlenemezse, tahsis kodu satır içine alınamaz. Daha da kötüsü, kaçış analizi başarısız olur ve nesne yığına ayrılamaz. Şanslıysanız, JVM'nin çalışma zamanı profili bu kod ısınırsa kurtarmaya gelebilir ve bu sınıfın hangi sınıfın baskın olduğunu ve bu sınıf için optimize edileceğini dinamik olarak belirleyebilir.

Bu iplikteki mikrobenç işaretlerin derinden kusurlu olduğunu unutmayın, bu yüzden onları bir tane tahıl ile alın. Şimdiye kadar en az kusurlu olan Peter Lawrey's: yöntemleri sarsmak için ısınma çalışmaları yapıyor ve (bilinçli olarak) tahsislerin gerçekten gerçekleştiğinden emin olmak için kaçış analizini yeniyor. Bununla birlikte, bunun bile sorunları vardır: örneğin, çok sayıda dizi deposunun önbellekleri yenmesi ve arabellekleri saklaması beklenebilir, bu nedenle tahsisleriniz çok hızlıysa bu genellikle bir hafıza ölçütü olacaktır. (Sonuç olarak doğru sonuca varması konusunda Peter'a Kudos: fark "2.5x" yerine "150ns" dir. Bunun yaşamak için bu tür bir şey yaptığından şüpheleniyorum.)


7

İlginçtir ki, güvenlik kontrollerini atlayan setAccessible (true) 'nun ayarlanması maliyette% 20'lik bir düşüşe sahiptir.

SetAccessible (true) olmadan

new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns

SetAccessible ile (true)

new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns

1
Prensipte benim için açık görünüyor. Bu sayılar, 1000000çağrıları çalıştırırken doğrusal olarak ölçekleniyor mu?
Lukas Eder

Aslında setAccessible(), özellikle birden fazla argümanı olan yöntemler için genel olarak çok daha fazla fark olabilir, bu yüzden her zaman çağrılmalıdır.
StaxMan

6

Evet, oldukça yavaş. Bunu yapan bazı kodları çalıştırıyorduk ve şu anda metrikler mevcut olmasa da, sonuç, yansımayı kullanmamak için bu kodu yeniden düzenlememiz gerekti. Sınıfın ne olduğunu biliyorsanız, doğrudan yapıcıyı arayın.


1
+1 Benzer bir deneyim yaşadım. Yansımayı yalnızca kesinlikle gerekliyse kullandığınızdan emin olmak iyidir.
Ryan Thames

örneğin AOP tabanlı kütüphanelerin düşünmeye ihtiyacı vardır.
gaurav

4

DoReflection () öğesinde, sınıfta çağrılan newInstance () yerine Class.forName ("misc.A") (potansiyel olarak sınıf sistemindeki sınıf yolunu taraması gerekir) nedeniyle ek yüktür. Class.forName ("misc.A") for-loop dışında yalnızca bir kez yapılırsa istatistiklerin nasıl görüneceğini merak ediyorum, gerçekten döngü her çağırma için yapılması gerekmez.


1

Evet, JVM kodu derleme zamanında optimize edemediğinden, yansımayla bir nesne oluşturmak her zaman daha yavaş olacaktır. Daha fazla ayrıntı için Sun / Java Yansıtma eğiticilerine bakın.

Bu basit teste bakın:

public class TestSpeed {
    public static void main(String[] args) {
        long startTime = System.nanoTime();
        Object instance = new TestSpeed();
        long endTime = System.nanoTime();
        System.out.println(endTime - startTime + "ns");

        startTime = System.nanoTime();
        try {
            Object reflectionInstance = Class.forName("TestSpeed").newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        endTime = System.nanoTime();
        System.out.println(endTime - startTime + "ns");
    }
}

3
Lookup ( Class.forName()) öğesini örneklemeden (newInstance ()) ayırmanız gerektiğini unutmayın , çünkü performans özelliklerinde önemli ölçüde farklılık gösterirler ve iyi tasarlanmış bir sistemde tekrarlanan aramayı bazen önleyebilirsiniz.
Joachim Sauer

3
Ayrıca: yararlı bir kıyaslama elde etmek için her görevi birçok kez yerine getirmeniz gerekir: her şeyden önce eylemler güvenilir bir şekilde ölçülemeyecek kadar yavaş ve ikinci olarak yararlı numaralar elde etmek için HotSpot VM'yi ısıtmanız gerekir.
Joachim Sauer

1

Genellikle Apache ortak BeanUtils veya PropertyUtils kullanabilirsiniz hangi introspection (temelde sınıflar hakkında meta veri önbelleğe böylece her zaman yansıma kullanmak gerekmez).


0

Hedef yöntemin ne kadar hafif / ağır olduğuna bağlı olduğunu düşünüyorum. Hedef yöntem çok hafifse (örneğin alıcı / ayarlayıcı), 1 ~ 3 kat daha yavaş olabilir. hedef yöntem yaklaşık 1 milisaniye veya daha fazla sürerse, performans çok yakın olacaktır. İşte Java 8 ve reflectasm ile yaptığım test :

public class ReflectionTest extends TestCase {    
    @Test
    public void test_perf() {
        Profiler.run(3, 100000, 3, "m_01 by refelct", () -> Reflection.on(X.class)._new().invoke("m_01")).printResult();    
        Profiler.run(3, 100000, 3, "m_01 direct call", () -> new X().m_01()).printResult();    
        Profiler.run(3, 100000, 3, "m_02 by refelct", () -> Reflection.on(X.class)._new().invoke("m_02")).printResult();    
        Profiler.run(3, 100000, 3, "m_02 direct call", () -> new X().m_02()).printResult();    
        Profiler.run(3, 100000, 3, "m_11 by refelct", () -> Reflection.on(X.class)._new().invoke("m_11")).printResult();    
        Profiler.run(3, 100000, 3, "m_11 direct call", () -> X.m_11()).printResult();    
        Profiler.run(3, 100000, 3, "m_12 by refelct", () -> Reflection.on(X.class)._new().invoke("m_12")).printResult();    
        Profiler.run(3, 100000, 3, "m_12 direct call", () -> X.m_12()).printResult();
    }

    public static class X {
        public long m_01() {
            return m_11();
        }    
        public long m_02() {
            return m_12();
        }    
        public static long m_11() {
            long sum = IntStream.range(0, 10).sum();
            assertEquals(45, sum);
            return sum;
        }    
        public static long m_12() {
            long sum = IntStream.range(0, 10000).sum();
            assertEquals(49995000, sum);
            return sum;
        }
    }
}

Test kodunun tamamı GitHub'da bulunabilir: ReflectionTest.java

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.