Yanıtlar:
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.
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.
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.
Class.getDeclaredMethod
) arar ve sonra Method.invoke
birden çok kez ararsam ? Yansımayı bir veya daha fazla çağırdığımda mı kullanıyorum? Takip eden soru, bunun yerine Method
bir ise Constructor
ve Constructor.newInstance
birden çok kez yaparsam ?
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.
"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.
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ı.
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ı
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.)
İ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
1000000
çağrıları çalıştırırken doğrusal olarak ölçekleniyor mu?
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.
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.
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.
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");
}
}
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.
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).
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