Jawaban:
Ya, tentu saja. Melihat ke atas kelas melalui refleksi adalah, dengan besarnya , lebih mahal.
Mengutip dokumentasi Java tentang refleksi :
Karena refleksi melibatkan jenis yang diselesaikan secara dinamis, optimasi mesin virtual Java tertentu tidak dapat dilakukan. Akibatnya, operasi reflektif memiliki kinerja lebih lambat daripada rekan non-reflektif mereka, dan harus dihindari di bagian kode yang sering disebut dalam aplikasi yang sensitif terhadap kinerja.
Inilah tes sederhana yang saya peretas dalam 5 menit pada mesin saya, menjalankan Sun JRE 6u10:
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);
}
}
Dengan hasil ini:
35 // no reflection
465 // using reflection
Ingatlah pencarian dan instantiasi dilakukan bersama-sama, dan dalam beberapa kasus pencarian dapat dihilangkan, tetapi ini hanyalah contoh dasar.
Bahkan jika Anda hanya instantiate, Anda masih mendapatkan kinerja yang baik:
30 // no reflection
47 // reflection using one lookup, only instantiating
Sekali lagi, YMMV.
Ya, ini lebih lambat.
Tapi ingat aturan # 1 sialan - OPTIMASI PREMATUR ADALAH ROOT DARI SEMUA JAHAT
(Yah, mungkin diikat dengan # 1 untuk KERING)
Aku bersumpah, jika seseorang mendatangiku di tempat kerja dan menanyakan ini padaku, aku akan sangat berhati-hati terhadap kode mereka selama beberapa bulan ke depan.
Anda tidak boleh mengoptimalkan sampai Anda yakin Anda membutuhkannya, sampai saat itu, cukup tulis kode yang baik dan mudah dibaca.
Oh, dan aku juga tidak bermaksud menulis kode bodoh. Hanya memikirkan cara terbersih yang dapat Anda lakukan - tidak ada salin dan tempel, dll. (Tetap waspada terhadap hal-hal seperti loop batin dan menggunakan koleksi yang paling sesuai dengan kebutuhan Anda - Mengabaikan ini bukan pemrograman "tidak dioptimalkan" , ini pemrograman "buruk")
Ini membuat saya takut ketika saya mendengar pertanyaan seperti ini, tetapi kemudian saya lupa bahwa setiap orang harus mempelajari semua peraturan sendiri sebelum mereka benar-benar mendapatkannya. Anda akan mendapatkannya setelah menghabiskan satu bulan bulan men-debug sesuatu yang "Dioptimalkan" oleh seseorang.
EDIT:
Hal yang menarik terjadi di utas ini. Periksa jawaban # 1, ini adalah contoh seberapa kuat kompiler dalam mengoptimalkan berbagai hal. Tes ini sepenuhnya tidak valid karena instantiasi non-reflektif dapat sepenuhnya difaktorkan.
Pelajaran? Jangan pernah optimalkan sampai Anda telah menulis solusi kode yang bersih dan rapi dan membuktikannya terlalu lambat.
Anda mungkin menemukan bahwa A a = new A () sedang dioptimalkan oleh JVM. Jika Anda meletakkan objek ke dalam array, mereka tidak berkinerja baik. ;) Cetakan berikut ini ...
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);
}
}
Ini menunjukkan perbedaannya adalah sekitar 150 ns pada mesin saya.
Class.getDeclaredMethod
) dan kemudian menelepon Method.invoke
beberapa kali? Apakah saya menggunakan refleksi sekali atau sebanyak yang saya minta? Pertanyaan lanjutan, bagaimana jika alih-alih Method
itu adalah Constructor
dan saya lakukan Constructor.newInstance
beberapa kali?
Jika memang ada kebutuhan untuk sesuatu yang lebih cepat daripada refleksi, dan itu bukan hanya optimasi prematur, maka bytecode generasi dengan ASM atau perpustakaan tingkat yang lebih tinggi adalah sebuah pilihan. Menghasilkan bytecode pertama kali lebih lambat daripada hanya menggunakan refleksi, tetapi begitu bytecode dibuat, itu secepat kode Java normal dan akan dioptimalkan oleh kompiler JIT.
Beberapa contoh aplikasi yang menggunakan pembuatan kode:
Meminta metode pada proksi yang dihasilkan oleh CGLIB sedikit lebih cepat dari proksi dinamis Java , karena CGLIB menghasilkan bytecode untuk prokinya, tetapi proksi dinamis hanya menggunakan refleksi ( saya mengukur CGLIB sekitar 10x lebih cepat dalam pemanggilan metode, tetapi membuat proksi lebih lambat).
JSerial menghasilkan bytecode untuk membaca / menulis bidang objek serial, alih-alih menggunakan refleksi. Ada beberapa tolok ukur di situs JSerial.
Saya tidak 100% yakin (dan saya tidak ingin membaca sumbernya sekarang), tetapi saya pikir Guice menghasilkan bytecode untuk melakukan injeksi ketergantungan. Koreksi saya jika saya salah.
"Signifikan" sepenuhnya tergantung pada konteks.
Jika Anda menggunakan refleksi untuk membuat objek handler tunggal berdasarkan pada beberapa file konfigurasi, dan kemudian menghabiskan sisa waktu Anda menjalankan query database, maka itu tidak signifikan. Jika Anda membuat banyak objek melalui refleksi dalam satu lingkaran ketat, maka ya, itu penting.
Secara umum, fleksibilitas desain (jika diperlukan!) Harus mendorong Anda menggunakan refleksi, bukan kinerja. Namun, untuk menentukan apakah kinerja merupakan masalah, Anda perlu membuat profil alih-alih mendapatkan respons sewenang-wenang dari forum diskusi.
Ada beberapa overhead dengan refleksi, tetapi jauh lebih kecil pada VM modern daripada sebelumnya.
Jika Anda menggunakan refleksi untuk membuat setiap objek sederhana di program Anda, maka ada sesuatu yang salah. Menggunakannya sesekali, ketika Anda memiliki alasan yang baik, seharusnya tidak menjadi masalah sama sekali.
Ya ada hit kinerja saat menggunakan Refleksi tetapi solusi yang mungkin untuk optimasi adalah dengan metode caching:
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");
akan menghasilkan:
[java] Metode panggilan 10.000 kali secara refleks dengan pencarian membutuhkan 5.618 mili
[java] Metode pemanggilan 1000000 kali secara refleks dengan cache membutuhkan 270 millis
Refleksi lambat, meskipun alokasi objek tidak seputus harapannya seperti aspek refleksi lainnya. Untuk mencapai kinerja yang setara dengan instantiasi berbasis refleksi mengharuskan Anda untuk menulis kode Anda sehingga jit dapat mengetahui kelas mana yang sedang dipakai. Jika identitas kelas tidak dapat ditentukan, maka kode alokasi tidak dapat digarisbawahi. Lebih buruk, analisis pelarian gagal, dan objek tidak dapat dialokasikan stack. Jika Anda beruntung, profil run-time JVM mungkin datang untuk menyelamatkan jika kode ini menjadi panas, dan dapat menentukan secara dinamis kelas mana yang mendominasi dan dapat mengoptimalkan untuk yang itu.
Ketahuilah bahwa microbenchmark pada utas ini sangat cacat, jadi bawalah mereka dengan sebutir garam. Yang paling tidak cacat sejauh ini adalah Peter Lawrey: ia melakukan pemanasan untuk mendapatkan metode yang tepat, dan (secara sadar) mengalahkan analisis pelarian untuk memastikan alokasi benar-benar terjadi. Meskipun ada masalah, misalnya: misalnya, sejumlah besar toko array dapat diperkirakan mengalahkan cache dan menyimpan buffer, jadi ini akan menjadi benchmark memori jika alokasi Anda sangat cepat. (Kudos kepada Peter karena mendapatkan kesimpulan yang tepat: bahwa perbedaannya adalah "150ns" daripada "2.5x". Saya curiga dia melakukan hal semacam ini untuk mencari nafkah.)
Cukup menarik, dengan menetapkan setAccessible (true), yang melewatkan pemeriksaan keamanan, memiliki pengurangan 20% dalam biaya.
Tanpa setAccessible (true)
new A(), 70 ns
A.class.newInstance(), 214 ns
new A(), 84 ns
A.class.newInstance(), 229 ns
Dengan setAccessible (true)
new A(), 69 ns
A.class.newInstance(), 159 ns
new A(), 85 ns
A.class.newInstance(), 171 ns
1000000
doa?
setAccessible()
dapat memiliki banyak perbedaan secara umum, terutama untuk metode dengan banyak argumen, jadi harus selalu disebut.
Ya, ini jauh lebih lambat. Kami menjalankan beberapa kode yang melakukan itu, dan sementara saya tidak memiliki metrik yang tersedia saat ini, hasil akhirnya adalah kami harus memperbaiki kode itu untuk tidak menggunakan refleksi. Jika Anda tahu apa kelasnya, cukup hubungi konstruktor secara langsung.
Dalam doReflection () adalah overhead karena Class.forName ("misc.A") (yang akan membutuhkan pencarian kelas, berpotensi memindai jalur kelas pada filsystem), daripada newInstance () dipanggil pada kelas. Saya bertanya-tanya seperti apa statistiknya jika Class.forName ("misc.A") dilakukan hanya sekali di luar for-loop, itu tidak benar-benar harus dilakukan untuk setiap doa dari loop.
Ya, selalu akan lebih lambat membuat objek dengan refleksi karena JVM tidak dapat mengoptimalkan kode pada waktu kompilasi. Lihat tutorial Sun / Java Reflection untuk lebih jelasnya.
Lihat tes sederhana ini:
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()
) dari instanciation (newInstance ()), karena mereka bervariasi secara signifikan dalam karakteristik kinerja mereka dan Anda kadang-kadang dapat menghindari pencarian berulang dalam sistem yang dirancang dengan baik.
Seringkali Anda dapat menggunakan Apache commons BeanUtils atau PropertyUtils yang melakukan introspeksi (pada dasarnya mereka men-cache data meta tentang kelas sehingga mereka tidak selalu perlu menggunakan refleksi).
Saya pikir itu tergantung pada seberapa ringan / berat metode targetnya. jika metode target sangat ringan (mis. pengambil / penyetel), itu bisa 1 ~ 3 kali lebih lambat. jika metode target memakan waktu sekitar 1 milidetik atau lebih, maka kinerjanya akan sangat dekat. di sini adalah tes yang saya lakukan dengan Java 8 dan reflectasm :
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;
}
}
}
Kode uji lengkap tersedia di GitHub: ReflectionTest.java