Dampak kinerja menggunakan instanceof di Jawa

314

Saya mengerjakan aplikasi dan satu pendekatan desain melibatkan penggunaan instanceofoperator yang sangat berat . Meskipun saya tahu bahwa desain OO umumnya mencoba untuk tidak menggunakan instanceof, itu adalah cerita yang berbeda dan pertanyaan ini murni terkait dengan kinerja. Saya bertanya-tanya apakah ada dampak kinerja? Apakah secepat ==?

Sebagai contoh, saya memiliki kelas dasar dengan 10 subclass. Dalam satu fungsi yang mengambil kelas dasar, saya memeriksa apakah kelas tersebut adalah turunan dari subkelas dan menjalankan beberapa rutin.

Salah satu cara lain yang saya pikirkan untuk menyelesaikannya adalah dengan menggunakan primitif "type id" integer, dan menggunakan bitmask untuk mewakili kategori dari subclass, dan kemudian hanya melakukan sedikit perbandingan topeng dari subclass "type id" ke topeng konstan mewakili kategori.

Apakah instanceofentah bagaimana dioptimalkan oleh JVM menjadi lebih cepat dari itu? Saya ingin tetap menggunakan Java tetapi kinerja aplikasi sangat penting. Akan sangat keren jika seseorang yang telah menyusuri jalan ini sebelumnya dapat menawarkan beberapa saran. Apakah saya terlalu banyak melakukan nitpicking atau berfokus pada hal yang salah untuk dioptimalkan?

Josh
sumber
81
Saya pikir inti dari pertanyaan ini adalah untuk mengesampingkan pertanyaan tentang praktik OO terbaik, dan memeriksa kinerjanya.
Dave L.
3
@Dave L. Biasanya saya setuju, tetapi OP tidak menyebutkan bahwa dia mencari beberapa teknik optimasi umum dan dia tidak yakin apakah masalahnya terkait dengan 'instanceof'. Saya pikir itu layak setidaknya menyebutkan desain 'benar' sehingga dia dapat membuat profil kedua pilihan.
Outlaw Programmer
51
Ugh ... Mengapa semua jawaban melewatkan inti pertanyaan dan memberikan retorika Knuth yang sama tentang optimisasi? Pertanyaan Anda adalah tentang apakah instanceof secara signifikan / mengejutkan lebih lambat daripada memeriksa objek kelas dengan ==, dan saya menemukan bahwa itu bukan.
gemuk
3
Performa instanceof dan casting cukup bagus. Saya memposting beberapa waktu di Java7 sekitar pendekatan yang berbeda untuk masalah di sini: stackoverflow.com/questions/16320014/…
Wheezil

Jawaban:

268

Kompiler JVM / JIC modern telah menghilangkan hit kinerja sebagian besar operasi "lambat" tradisional, termasuk instance, penanganan pengecualian, refleksi, dll.

Seperti yang ditulis Donald Knuth, "Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan." Kinerja instanceof mungkin tidak akan menjadi masalah, jadi jangan buang waktu Anda untuk mencari solusi eksotis sampai Anda yakin itu masalahnya.

Steve
sumber
13
Modern JVM / JIC .. Bisakah Anda sebutkan dari versi java mana optimisasi ini telah dibahas?
Ravisha
138
Selalu ada seseorang, yang mengutip Knuth ketika kinerja adalah topik ... Lupa, bahwa Knuth juga menyatakan (dalam artikel yang sama) "Dalam disiplin ilmu teknik yang mapan, peningkatan 12%, mudah didapat, tidak pernah dianggap marginal dan saya percaya sudut pandang yang sama harus menang dalam rekayasa perangkat lunak ", hampir semua karyanya adalah tentang efisiensi algoritma dan dia menulis algoritma dalam perakitan untuk (antara lain) mencapai kinerja yang lebih baik. Meh ...
kgadek
4
Di samping sini tapi apakah try { ObjT o = (ObjT)object } catch (e) { no not one of these }akan lebih cepat lebih lambat ??
peterk
35
Jika "objek" adalah instance dari ObjT, casting itu sedikit lebih cepat daripada melakukan instanceof, tetapi perbedaan tes cepat saya temukan adalah 10-20ms lebih dari 10.000.000 iterasi. Namun, jika "objek" bukan ObjT, menangkap pengecualian lebih dari 3000x lebih lambat - lebih dari 31.000 ms vs ~ 10 ms untuk instanceof.
Steve
19
argumen kuat seperti itu tanpa "referensi", sama sekali tidak berguna karena hanya dikemukakan pendapat.
marcorossi
279

Pendekatan

Saya menulis program benchmark untuk mengevaluasi implementasi yang berbeda:

  1. instanceof implementasi (sebagai referensi)
  2. berorientasi objek melalui kelas abstrak dan @Overridemetode pengujian
  3. menggunakan implementasi tipe sendiri
  4. getClass() == _.class penerapan

Saya menggunakan jmh untuk menjalankan benchmark dengan 100 panggilan pemanasan, 1000 iterasi di bawah pengukuran, dan dengan 10 fork. Jadi setiap opsi diukur dengan 10.000 kali, yang membutuhkan 12:18:57 untuk menjalankan seluruh tolok ukur pada MacBook Pro saya dengan macOS 10.12.4 dan Java 1.8. Benchmark mengukur waktu rata-rata setiap opsi. Untuk lebih jelasnya lihat implementasi saya di GitHub .

Demi kelengkapan: Ada versi sebelumnya dari jawaban ini dan tolok ukur saya .

Hasil

| Operasi | Runtime dalam nanodetik per operasi | Relatif terhadap instance |
| ------------ | ------------------------------------ - | ------------------------ |
| INSTANCEOF | 39.598 ± 0,022 ns / op | 100,00% |
| GETCLASS | 39.687 ± 0,021 ns / op | 100,22% |
| TYPE | 46.295 ± 0,026 ns / op | 116,91% |
| OO | 48.078 ± 0,026 ns / op | 121,42% |

tl; dr

Di Jawa 1.8 instanceofadalah pendekatan tercepat, meskipun getClass()sangat dekat.

Michael Dorner
sumber
58
+0.(9)untuk sains!
16
+ 0,1 lainnya dari saya: D
Tobias Reich
14
@TobiasReich Jadi kami dapat +1.0(9). :)
Pavel
9
Saya tidak berpikir ini mengukur sesuatu yang berarti sama sekali. Kode ini mengukur penggunaan System.currentTimeMillis()pada operasi yang tidak lebih dari pemanggilan metode tunggal, yang harus memberikan banyak presisi rendah. Gunakan kerangka kerja benchmark seperti JMH sebagai gantinya!
Lii
6
Atau lakukan saja pengaturan waktu seluruh panggilan miliar daripada per panggilan.
LegendLength
74

Saya baru saja membuat tes sederhana untuk melihat bagaimana kinerja instanceOf dibandingkan dengan panggilan s.equals () sederhana ke objek string dengan hanya satu huruf.

dalam loop 10.000.000 instanceOf memberi saya 63-96ms, dan string yang sama memberi saya 106-230ms

Saya menggunakan java jvm 6.

Jadi dalam tes sederhana saya lebih cepat untuk melakukan instanceOf daripada perbandingan string satu karakter.

menggunakan Integer .equals () alih-alih string memberi saya hasil yang sama, hanya ketika saya menggunakan == saya lebih cepat daripada instanceOf oleh 20ms (dalam 10.000.000 loop)


sumber
4
Apakah mungkin bagi Anda untuk mengirim kode di sini? Itu akan luar biasa!
The Alchemist
7
Bagaimana instanceOf dibandingkan dengan pengiriman fungsi polimorfik?
Chris
21
Mengapa Anda membandingkan instanceof dengan String.equals ()? Jika Anda ingin memeriksa jenisnya, Anda harus object.getClass (). Equals (SomeType.class)
marsbear
4
@ marsbear equals()tidak akan memotongnya, karena subkelas; kamu membutuhkan isAssignableFrom().
David Moles
1
@ Marsbear Benar, tapi itu bukan ujian yang lebih baik dari apa yang diminta OP.
David Moles
20

Item yang akan menentukan dampak kinerja adalah:

  1. Jumlah kelas yang mungkin yang mana instance dari operator dapat mengembalikan true
  2. Distribusi data Anda - apakah sebagian besar operasi contoh diselesaikan dalam upaya pertama atau kedua? Anda harus mengutamakan kemungkinan untuk mengembalikan operasi yang sebenarnya terlebih dahulu.
  3. Lingkungan penyebaran. Menjalankan pada Sun Solaris VM jauh berbeda dari Sun Windows JVM. Solaris akan berjalan dalam mode 'server' secara default, sedangkan Windows akan berjalan dalam mode klien. Optimalisasi JIT pada Solaris, akan membuat semua akses metode dapat sama.

Saya membuat microbenchmark untuk empat metode pengiriman yang berbeda . Hasil dari Solaris adalah sebagai berikut, dengan jumlah yang lebih kecil lebih cepat:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
brianegge
sumber
18

Menjawab pertanyaan terakhir Anda: Kecuali seorang profiler memberi tahu Anda, bahwa Anda menghabiskan jumlah waktu yang konyol dalam contoh: Ya, Anda benar-benar gila.

Sebelum bertanya-tanya tentang mengoptimalkan sesuatu yang tidak perlu dioptimalkan: Tulis algoritma Anda dengan cara yang paling mudah dibaca dan jalankan. Jalankan, sampai jit-compiler mendapat kesempatan untuk mengoptimalkannya sendiri. Jika kemudian Anda memiliki masalah dengan kode ini, gunakan profiler untuk memberi tahu Anda, di mana mendapatkan yang terbaik dan optimalkan ini.

Pada saat kompiler yang sangat optimal, tebakan Anda tentang kemacetan kemungkinan besar akan benar-benar salah.

Dan dalam semangat sejati dari jawaban ini (yang saya yakini dengan sepenuh hati): Saya benar-benar tidak tahu bagaimana instanceof dan == berhubungan ketika jit-compiler mendapat kesempatan untuk mengoptimalkannya.

Saya lupa: Jangan pernah mengukur putaran pertama.

Olaf Kock
sumber
1
Tetapi poster asli menyebutkan kinerja sangat penting untuk aplikasi ini, jadi tidak masuk akal untuk mengoptimalkan awal dalam situasi itu. Dengan kata lain Anda tidak akan menulis game 3d dalam GWBasic dan kemudian pada akhirnya berkata, ok mari kita mulai mengoptimalkan ini, langkah pertama adalah port ke c ++.
LegendLength
GWBasic mungkin merupakan awal yang baik untuk gim 3d, jika ada perpustakaan yang tepat tersedia. Tapi itu samping (karena ini argumen buatan): OP tidak meminta penulisan ulang lengkap sebagai optimasi. Ini tentang satu konstruksi di mana kita bahkan tidak tahu apakah dampaknya signifikan (bahkan jika ada cara berkinerja lebih baik untuk melakukan hal yang sama dalam versi kompiler saat ini ). Saya berdiri kokoh di belakang c2.com/cgi/wiki?ProfileBeforeOptimizing dan jawaban saya. Optimalisasi Awal adalah akar dari semua kejahatan! Itu membuat pemeliharaan lebih sulit - dan pemeliharaan adalah aspek yang perlu dioptimalkan
Olaf Kock
15

Saya punya pertanyaan yang sama, tetapi karena saya tidak menemukan 'metrik kinerja' untuk use case yang mirip dengan milik saya, saya telah melakukan beberapa kode sampel lagi. Pada perangkat keras saya dan Java 6 & 7, perbedaan antara instanceof dan mengaktifkan iterasi 10mln adalah

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms

Jadi, instanceof benar-benar lebih lambat, terutama pada sejumlah besar pernyataan if-else-if, namun perbedaan akan diabaikan dalam aplikasi nyata.

import java.util.Date;

public class InstanceOfVsEnum {

    public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;

    public static class Handler {
        public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
        protected Handler(Type type) { this.type = type; }
        public final Type type;

        public static void addHandlerInstanceOf(Handler h) {
            if( h instanceof H1) { c1++; }
            else if( h instanceof H2) { c2++; }
            else if( h instanceof H3) { c3++; }
            else if( h instanceof H4) { c4++; }
            else if( h instanceof H5) { c5++; }
            else if( h instanceof H6) { c6++; }
            else if( h instanceof H7) { c7++; }
            else if( h instanceof H8) { c8++; }
            else if( h instanceof H9) { c9++; }
            else if( h instanceof HA) { cA++; }
        }

        public static void addHandlerSwitch(Handler h) {
            switch( h.type ) {
                case Type1: c1++; break;
                case Type2: c2++; break;
                case Type3: c3++; break;
                case Type4: c4++; break;
                case Type5: c5++; break;
                case Type6: c6++; break;
                case Type7: c7++; break;
                case Type8: c8++; break;
                case Type9: c9++; break;
                case TypeA: cA++; break;
            }
        }
    }

    public static class H1 extends Handler { public H1() { super(Type.Type1); } }
    public static class H2 extends Handler { public H2() { super(Type.Type2); } }
    public static class H3 extends Handler { public H3() { super(Type.Type3); } }
    public static class H4 extends Handler { public H4() { super(Type.Type4); } }
    public static class H5 extends Handler { public H5() { super(Type.Type5); } }
    public static class H6 extends Handler { public H6() { super(Type.Type6); } }
    public static class H7 extends Handler { public H7() { super(Type.Type7); } }
    public static class H8 extends Handler { public H8() { super(Type.Type8); } }
    public static class H9 extends Handler { public H9() { super(Type.Type9); } }
    public static class HA extends Handler { public HA() { super(Type.TypeA); } }

    final static int cCycles = 10000000;

    public static void main(String[] args) {
        H1 h1 = new H1();
        H2 h2 = new H2();
        H3 h3 = new H3();
        H4 h4 = new H4();
        H5 h5 = new H5();
        H6 h6 = new H6();
        H7 h7 = new H7();
        H8 h8 = new H8();
        H9 h9 = new H9();
        HA hA = new HA();

        Date dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerInstanceOf(h1);
            Handler.addHandlerInstanceOf(h2);
            Handler.addHandlerInstanceOf(h3);
            Handler.addHandlerInstanceOf(h4);
            Handler.addHandlerInstanceOf(h5);
            Handler.addHandlerInstanceOf(h6);
            Handler.addHandlerInstanceOf(h7);
            Handler.addHandlerInstanceOf(h8);
            Handler.addHandlerInstanceOf(h9);
            Handler.addHandlerInstanceOf(hA);
        }
        System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));

        dtStart = new Date();
        for( int i = 0; i < cCycles; i++ ) {
            Handler.addHandlerSwitch(h1);
            Handler.addHandlerSwitch(h2);
            Handler.addHandlerSwitch(h3);
            Handler.addHandlerSwitch(h4);
            Handler.addHandlerSwitch(h5);
            Handler.addHandlerSwitch(h6);
            Handler.addHandlerSwitch(h7);
            Handler.addHandlerSwitch(h8);
            Handler.addHandlerSwitch(h9);
            Handler.addHandlerSwitch(hA);
        }
        System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
    }
}
Xtra Coder
sumber
Hasil mana yang java 6 dan mana yang java 7? Sudahkah Anda meninjau kembali ini di bawah Java 8? Lebih penting di sini, Anda membandingkan panjang jika contoh dengan apa yang penting pernyataan kasus di int. Saya pikir kita akan berharap saklar int menjadi lebih cepat.
Azeroth2b
1
Saya tidak ingat persis apa yang terjadi 5 tahun yang lalu - saya pikir Java 6 dan Java 7 memiliki hasil yang sama, itu sebabnya hanya ada satu hasil yang disediakan (asalkan 2 baris untuk kedalaman hirarki kelas yang berbeda) ... dan tidak ada , saya tidak mencoba membandingkan dengan Java 8. Seluruh kode tes disediakan - Anda dapat menyalin / menempelnya dan memeriksa di lingkungan yang Anda butuhkan (perhatikan - saat ini saya akan menggunakan tes JMH untuk ini).
Xtra Coder
9

instanceof sangat cepat, hanya mengambil beberapa instruksi CPU.

Rupanya, jika suatu kelas Xtidak memiliki subclass dimuat (JVM tahu), instanceofdapat dioptimalkan sebagai:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

Biaya utama hanya untuk membaca!

Jika Xmemang ada subclass dimuat, diperlukan beberapa bacaan lagi; mereka kemungkinan terletak bersama sehingga biaya tambahan juga sangat rendah.

Berita baik semuanya!

tak dapat disangkal
sumber
2
dapat dioptimalkan atau yang dioptimalkan? sumber?
@vaxquis dapat sebagai jvm impl spesifiknya
RecursiveExceptionException
@itzJanuary mendesah Anda melewatkan titik pertanyaan saya di sini: semua orang tahu bahwa compiler dapat mengoptimalkan foo- tetapi ini foobenar-benar saat ini dioptimalkan oleh Oracle javac / VM - atau hanya mungkin bahwa ia akan melakukan itu di masa depan? Juga, saya bertanya kepada penjawab apakah dia memiliki sumber dukungan (baik itu dokumen, kode sumber, blog dev) yang mendokumentasikan bahwa itu memang dapat dioptimalkan atau dioptimalkan ? Tanpanya, jawaban ini hanyalah beberapa pemikiran acak tentang apa yang dapat dilakukan oleh kompiler .
@vaxquis Anda tidak pernah menyebutkan VM Hotspot tetapi dalam kasus itu saya tidak tahu apakah itu "dioptimalkan".
RecursiveExceptionException
1
Baru-baru ini membaca bahwa JIT (JVM 8) akan mengoptimalkan situs panggilan untuk 1 atau 2 jenis melalui panggilan langsung, tetapi kembali ke vtable jika lebih dari dua jenis aktual ditemukan. Jadi hanya memiliki dua jenis konkret yang melewati situs panggilan saat runtime mewakili keunggulan kinerja.
simon.watts
5

Instanceof sangat cepat. Itu bermuara pada bytecode yang digunakan untuk perbandingan referensi kelas. Coba beberapa juta instance dalam satu lingkaran dan lihat sendiri.

Apocalisp
sumber
5

instanceof mungkin akan lebih mahal daripada yang sederajat dalam implementasi dunia nyata (yaitu, yang mana instanceof benar-benar dibutuhkan, dan Anda tidak bisa menyelesaikannya dengan mengganti metode umum, seperti setiap buku teks pemula serta Demian di atas menyarankan).

Mengapa demikian? Karena apa yang mungkin akan terjadi adalah bahwa Anda memiliki beberapa antarmuka, yang menyediakan beberapa fungsi (katakanlah, antarmuka x, y dan z), dan beberapa objek untuk memanipulasi yang mungkin (atau tidak) mengimplementasikan salah satu antarmuka tersebut ... tetapi tidak secara langsung. Katakanlah, misalnya, saya punya:

w meluas x

A mengimplementasikan w

B meluas A

C memanjang B, mengimplementasikan y

D meluas C, mengimplementasikan z

Misalkan saya sedang memproses instance D, objek d. Komputasi (d instanceof x) perlu mengambil d.getClass (), loop melalui antarmuka yang diterapkan untuk mengetahui apakah seseorang == ke x, dan jika tidak melakukannya lagi secara rekursif untuk semua leluhur mereka ... Dalam kasus kami, jika Anda melakukan penjelajahan luas pertama dari pohon itu, menghasilkan setidaknya 8 perbandingan, seandainya y dan z tidak memperpanjang apa pun ...

Kompleksitas pohon derivasi dunia nyata cenderung lebih tinggi. Dalam beberapa kasus, JIT dapat mengoptimalkan sebagian besar darinya, jika ia dapat menyelesaikan di muka d sebagai, dalam semua kasus yang mungkin, sebuah instance dari sesuatu yang memanjang x. Namun, secara realistis, Anda akan sering melewati jalur pohon itu.

Jika itu menjadi masalah, saya akan menyarankan menggunakan peta handler sebagai gantinya, menghubungkan kelas konkret objek ke penutupan yang melakukan penanganan. Ini menghilangkan fase traversal pohon yang mendukung pemetaan langsung. Namun, berhati-hatilah bahwa jika Anda telah menetapkan handler untuk C.class, objek saya d di atas tidak akan dikenali.

di sini adalah 2 sen saya, saya harap mereka membantu ...


sumber
5

instanceof sangat efisien, sehingga kinerja Anda tidak mungkin menderita. Namun, menggunakan banyak instanceof menunjukkan masalah desain.

Jika Anda dapat menggunakan xClass == String.class, ini lebih cepat. Catatan: Anda tidak perlu instanceof untuk kelas akhir.

Peter Lawrey
sumber
1
Btw apa yang Anda maksud dengan "tidak perlu instanceof untuk kelas akhir"?
Pacerier
Kelas akhir tidak dapat memiliki sub kelas. Dalam hal x.getClass() == Class.classini sama denganx instanceof Class
Peter Lawrey
Keren, dengan anggapan bahwa x bukan nol, mana yang Anda pilih?
Pacerier
Poin yang bagus. Itu akan tergantung pada apakah saya berharap xdemikian null. (Atau yang lebih jelas)
Peter Lawrey
Hmm saya baru sadar bahwa kita bisa menggunakan java.lang.class.isAssignableFrom juga, apakah Anda sadar jika instanceof kata kunci secara internal menggunakan fungsi seperti ini?
Pacerier
4

Secara umum alasan mengapa operator "instanceof" disukai dalam kasus seperti itu (di mana instanceof sedang memeriksa untuk subclass dari kelas dasar ini) adalah karena apa yang harus Anda lakukan adalah memindahkan operasi ke dalam metode dan menimpanya untuk yang sesuai subkelas. Misalnya, jika Anda memiliki:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

Anda dapat menggantinya dengan

o.doEverything();

dan kemudian memiliki implementasi "doEverything ()" di panggilan Class1 "doThis ()", dan di panggilan Class2 "doThat ()", dan seterusnya.

Paul Tomblin
sumber
11
Tetapi terkadang Anda tidak bisa. Jika Anda mengimplementasikan antarmuka yang telah Anda ambil dalam sebuah Objek, dan Anda perlu menentukan jenisnya, maka instanceof adalah satu-satunya pilihan. Anda dapat mencoba casting, tetapi instanceof umumnya lebih bersih.
Herms
4

'instanceof' sebenarnya adalah operator, seperti + atau -, dan saya percaya bahwa ia memiliki instruksi bytecode JVM sendiri. Ini harusnya cepat.

Saya seharusnya tidak bahwa jika Anda memiliki saklar di mana Anda menguji apakah suatu objek adalah turunan dari beberapa subkelas, maka desain Anda mungkin perlu dikerjakan ulang. Pertimbangkan untuk mendorong perilaku khusus subkelas ke dalam subkelas itu sendiri.

Programmer Hukum
sumber
4

Demian dan Paul menyebutkan poin yang bagus; namun , penempatan kode untuk dieksekusi benar-benar tergantung pada bagaimana Anda ingin menggunakan data ...

Saya penggemar berat objek data kecil yang dapat digunakan dalam banyak cara. Jika Anda mengikuti pendekatan override (polymorphic), objek Anda hanya dapat digunakan "satu arah".

Di sinilah pola masuk ...

Anda dapat menggunakan pengiriman ganda (seperti dalam pola pengunjung) untuk meminta setiap objek untuk "memanggil Anda" dengan sendirinya - ini akan menyelesaikan jenis objek. Namun (lagi) Anda membutuhkan kelas yang dapat "melakukan hal-hal" dengan semua subtipe yang mungkin.

Saya lebih suka menggunakan pola strategi, di mana Anda dapat mendaftarkan strategi untuk setiap subtipe yang ingin Anda tangani. Sesuatu seperti yang berikut ini. Perhatikan bahwa ini hanya membantu untuk pencocokan jenis yang tepat, tetapi memiliki keunggulan yang dapat diperpanjang - kontributor pihak ketiga dapat menambahkan jenis dan penangan mereka sendiri. (Ini bagus untuk kerangka kerja dinamis seperti OSGi, di mana bundel baru dapat ditambahkan)

Semoga ini akan menginspirasi beberapa ide lain ...

package com.javadude.sample;

import java.util.HashMap;
import java.util.Map;

public class StrategyExample {
    static class SomeCommonSuperType {}
    static class SubType1 extends SomeCommonSuperType {}
    static class SubType2 extends SomeCommonSuperType {}
    static class SubType3 extends SomeCommonSuperType {}

    static interface Handler<T extends SomeCommonSuperType> {
        Object handle(T object);
    }

    static class HandlerMap {
        private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
            new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
        public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
            handlers_.put(c, handler);
        }
        @SuppressWarnings("unchecked")
        public <T extends SomeCommonSuperType> Object handle(T o) {
            return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
        }
    }

    public static void main(String[] args) {
        HandlerMap handlerMap = new HandlerMap();

        handlerMap.add(SubType1.class, new Handler<SubType1>() {
            @Override public Object handle(SubType1 object) {
                System.out.println("Handling SubType1");
                return null;
            } });
        handlerMap.add(SubType2.class, new Handler<SubType2>() {
            @Override public Object handle(SubType2 object) {
                System.out.println("Handling SubType2");
                return null;
            } });
        handlerMap.add(SubType3.class, new Handler<SubType3>() {
            @Override public Object handle(SubType3 object) {
                System.out.println("Handling SubType3");
                return null;
            } });

        SubType1 subType1 = new SubType1();
        handlerMap.handle(subType1);
        SubType2 subType2 = new SubType2();
        handlerMap.handle(subType2);
        SubType3 subType3 = new SubType3();
        handlerMap.handle(subType3);
    }
}
Scott Stanchfield
sumber
4

Saya menulis tes kinerja berdasarkan jmh-java-benchmark-archetype: 2.21. JDK adalah openjdk dan versinya 1.8.0_212. Mesin uji adalah mac pro. Hasil tes adalah:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us

Hasilnya menunjukkan bahwa: getClass lebih baik daripada instanceOf, yang bertentangan dengan tes lainnya. Namun, saya tidak tahu mengapa.

Kode tes di bawah ini:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}
salexinx
sumber
Jika saya berspekulasi, apa yang dilakukan insting ini bisa dibilang lebih kompleks. Pemeriksaan getClass () == akan melakukan pemeriksaan tepat 1: 1, di mana instanceof memeriksa hierarki mis. Koleksi myHashSet instanceof akan lulus, tetapi myHashSet.getClass () == Collection.class tidak mau. Pada dasarnya, mereka bukan operasi yang setara, jadi saya tidak terlalu terkejut bahwa kinerjanya juga berbeda.
AMTerp
3

Sulit untuk mengatakan bagaimana JVM tertentu mengimplementasikan instance, tetapi dalam kebanyakan kasus, Object dapat dibandingkan dengan struct dan kelas juga dan setiap struct objek memiliki pointer ke struct kelas itu adalah instance. Jadi sebenarnya contoh untuk

if (o instanceof java.lang.String)

mungkin secepat kode C berikut

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

dengan asumsi kompiler JIT sudah ada dan melakukan pekerjaan yang layak.

Mempertimbangkan bahwa ini hanya mengakses sebuah pointer, mendapatkan sebuah pointer pada suatu titik offset tertentu pada pointer dan membandingkannya dengan pointer yang lain (yang pada dasarnya sama dengan pengujian ke angka 32 bit yang sama), saya akan mengatakan bahwa operasi dapat benar-benar sangat cepat.

Tidak harus, meskipun, itu sangat tergantung pada JVM. Namun, jika ini akan menjadi operasi bottleneck dalam kode Anda, saya akan menganggap implementasi JVM agak buruk. Bahkan yang tidak memiliki kompiler JIT dan hanya kode interpretasi yang dapat membuat instanceof tes dalam waktu singkat.

Mecki
sumber
1
Tidakkah harus mencari tahu apakah o mewarisi dari java.lang.String?
WW.
1
Itu sebabnya saya mengatakan itu "mungkin" secepat. Pada kenyataannya ia melakukan loop, pertama memeriksa iAmInstanceOf terhadap kelas yang bersangkutan, kemudian naik ke atas pohon warisan o dan mengulangi pemeriksaan ini untuk setiap super-kelas o (sehingga mungkin harus menjalankan loop ini beberapa kali) untuk sebuah pertandingan)
Mecki
3

Saya akan kembali kepada Anda atas kinerja. Tetapi cara untuk menghindari masalah (atau ketiadaan) sama sekali adalah dengan membuat antarmuka induk untuk semua subclass di mana Anda perlu melakukan instanceof. Antarmuka akan menjadi super set semua metode dalam sub-kelas yang Anda perlu lakukan instance of check. Jika suatu metode tidak berlaku untuk sub-kelas tertentu, cukup sediakan implementasi tiruan dari metode ini. Jika saya tidak salah memahami masalah ini, ini adalah bagaimana saya menyelesaikan masalah di masa lalu.

Jose Quijada
sumber
2

InstanceOf adalah peringatan desain Object Oriented yang buruk.

JVM saat ini berarti instanceOf tidak terlalu mengkhawatirkan kinerja. Jika Anda mendapati diri Anda sering menggunakannya, terutama untuk fungsionalitas inti, mungkin inilah saatnya untuk melihat desainnya. Peningkatan kinerja (dan kesederhanaan / pemeliharaan) dari refactoring ke desain yang lebih baik akan jauh lebih besar daripada siklus prosesor aktual yang dihabiskan untuk panggilan instanceOf sebenarnya .

Untuk memberikan contoh pemrograman sederhana yang sangat kecil.

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

Apakah arsitektur yang buruk adalah pilihan yang lebih baik untuk memiliki SomeObject menjadi kelas induk dari dua kelas anak di mana setiap kelas anak menimpa metode (doSomething) sehingga kode akan terlihat seperti ini:

Someobject.doSomething();
Demian Krige
sumber
61
Saya sadar akan hal itu. Bukan itu yang saya minta.
Josh
Tidak yakin apakah akan memilih ini atau tidak karena ini adalah poin yang baik, tetapi tidak menjawab pertanyaan yang diajukan ...
jklp
7
Saya pikir contoh kode sebenarnya sangat buruk: Anda tidak dapat memperluas kelas Double, dan Anda juga tidak dapat memperoleh Double dari beberapa kelas lain. Jika Anda menggunakan kelas lain sebagai contoh, itu akan baik-baik saja.
Lena Schimmel
6
Juga jika kelas anak-anak dari SomeObject adalah objek nilai, maka Anda tidak ingin memasukkan logika di dalamnya. Misalnya Pie dan Roast mungkin bukan tempat yang tepat untuk logika putInOven () dan putInMouth ().
sk.
pai dan daging panggang sendiri akan luar biasa
binboavetonik
2

Dalam versi Java modern, instanceof operator lebih cepat sebagai panggilan metode sederhana. Ini berarti:

if(a instanceof AnyObject){
}

lebih cepat dari:

if(a.getType() == XYZ){
}

Hal lain adalah jika Anda perlu membuat banyak contoh. Kemudian sakelar yang hanya memanggil sekali getType () lebih cepat.

Horcrux7
sumber
1

Jika kecepatan adalah satu-satunya tujuan Anda maka menggunakan konstanta int untuk mengidentifikasi sub-kelas tampaknya mencukur milidetik waktu itu

static final int ID_A = 0;
static final int ID_B = 1;
abstract class Base {
  final int id;
  Base(int i) { id = i; }
}
class A extends Base {
 A() { super(ID_A); }
}
class B extends Base {
 B() { super(ID_B); }
}
...
Base obj = ...
switch(obj.id) {
case  ID_A: .... break;
case  ID_B: .... break;
}

desain OO yang mengerikan, tetapi jika analisis kinerja Anda menunjukkan ini adalah tempat Anda mengalami kemacetan maka mungkin. Dalam kode saya, kode pengiriman membutuhkan 10% dari total waktu eksekusi dan ini mungkin berkontribusi pada peningkatan kecepatan total 1%.

Salix alba
sumber
0

Anda harus mengukur / profil jika itu benar-benar masalah kinerja dalam proyek Anda. Jika itu saya sarankan desain ulang - jika mungkin. Saya cukup yakin Anda tidak bisa mengalahkan implementasi asli platform (ditulis dalam C). Anda juga harus mempertimbangkan pewarisan berganda dalam kasus ini.

Anda harus memberi tahu lebih banyak tentang masalahnya, mungkin Anda bisa menggunakan toko asosiatif, misalnya Peta <Kelas, Objek> jika Anda hanya tertarik pada jenis beton.

Karl
sumber
0

Berkenaan dengan catatan Peter Lawrey bahwa Anda tidak perlu instanceof untuk kelas akhir dan hanya dapat menggunakan referensi kesetaraan, hati-hati! Meskipun kelas akhir tidak dapat diperpanjang, mereka tidak dijamin dimuat oleh classloader yang sama. Hanya gunakan x.getClass () == SomeFinal.class atau sejenisnya jika Anda benar-benar positif bahwa hanya ada satu classloader yang dimainkan untuk bagian kode itu.


sumber
4
Jika suatu kelas dimuat oleh classloader yang berbeda, saya tidak berpikir instanceof akan cocok juga.
Peter Lawrey
0

Saya juga lebih suka pendekatan enum, tapi saya akan menggunakan kelas dasar abstrak untuk memaksa subclass untuk mengimplementasikan getType()metode ini.

public abstract class Base
{
  protected enum TYPE
  {
    DERIVED_A, DERIVED_B
  }

  public abstract TYPE getType();

  class DerivedA extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_A;
    }
  }

  class DerivedB extends Base
  {
    @Override
    public TYPE getType()
    {
      return TYPE.DERIVED_B;
    }
  }
}
mike
sumber
0

Saya pikir mungkin layak mengirimkan contoh tandingan ke konsensus umum di halaman ini bahwa "instanceof" tidak cukup mahal untuk dikhawatirkan. Saya menemukan saya memiliki beberapa kode dalam loop batin yang (dalam beberapa upaya bersejarah optimasi) lakukan

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

di mana memanggil head () pada SingleItem mengembalikan nilai tidak berubah. Mengganti kode dengan

seq = seq.head();

memberi saya kecepatan dari 269ms ke 169ms, terlepas dari kenyataan bahwa ada beberapa hal yang cukup berat terjadi dalam loop, seperti konversi string-to-double. Tentu saja mungkin bahwa percepatan lebih disebabkan oleh penghapusan cabang bersyarat daripada menghilangkan instance dari operator itu sendiri; tapi saya pikir itu layak disebut.

Michael Kay
sumber
Ini mungkin karena ifitu sendiri. Jika distribusi trues dan falses mendekati genap, eksekusi spekulatif menjadi tidak berguna, yang mengarah pada kelambatan yang signifikan.
Dmytro
-4

Anda fokus pada hal yang salah. Perbedaan antara instanceof dan metode lain untuk memeriksa hal yang sama mungkin bahkan tidak dapat diukur. Jika kinerja sangat penting maka Java mungkin bahasa yang salah. Alasan utama adalah bahwa Anda tidak dapat mengontrol ketika VM memutuskan ingin mengumpulkan sampah, yang dapat mengambil CPU hingga 100% selama beberapa detik dalam program besar (MagicDraw 10 sangat bagus untuk itu). Kecuali Anda mengendalikan setiap komputer, program ini akan berjalan pada Anda tidak dapat menjamin versi JVM mana yang akan dihidupkan, dan banyak dari yang lebih tua memiliki masalah kecepatan utama. Jika itu adalah aplikasi kecil Anda mungkin tidak masalah dengan Java, tetapi jika Anda terus membaca dan membuang data, maka Anda akan melihat kapan GC masuk.

tloach
sumber
7
Itu jauh lebih tidak benar dari algoritma pengumpulan sampah java yang lebih modern daripada sebelumnya. Bahkan algoritma paling sederhana tidak peduli lagi berapa banyak memori yang Anda buang setelah Anda menggunakannya - mereka hanya peduli berapa banyak yang disimpan di koleksi generasi muda.
Bill Michell
3
Hebat, kecuali saya menggunakan JVM terbaru dan komputer saya masih merangkak ketika GC berjalan. Pada server ram 3GB dual-core. Java bukan bahasa yang digunakan jika kinerja benar-benar penting.
menyapa
@ David: Anda tidak perlu meminta waktu-nyata untuk memiliki masalah ketika aplikasi Anda hilang untuk jangka waktu tertentu. Yang menyenangkan yang saya temui adalah aplikasi java yang terhubung ke aliran TCP yang mati ketika GC berjalan karena tidak menutup aliran terlebih dahulu dan tidak bisa menangani kelebihan lalu lintas jaringan ketika kembali - itu akan segera masuk ke loop di mana GC berjalan, ketika aplikasi melanjutkannya mencoba untuk churn melalui banyak data, yang membuatnya kehabisan memori, yang memicu GC, dll. Java sangat bagus untuk banyak tugas, tetapi bukan tugas di mana sangat kinerja yang kuat adalah persyaratan.
tloach
6
@tloach terdengar bagiku seperti desain aplikasi yang buruk. Anda berbicara tentang "kinerja" seolah-olah itu satu dimensi. Saya telah bekerja dengan (dan terus) banyak aplikasi java yang, misalnya, pemain dalam memberikan analisis statistik interaktif dan visualisasi set data yang sangat besar, atau pemain dalam memproses volume transaksi yang sangat besar dengan sangat cepat. "kinerja" bukan hanya satu hal, dan fakta bahwa seseorang dapat menulis aplikasi yang mengelola memori dengan buruk dan memungkinkan GC mendapatkan caranya sendiri tidak berarti apa pun yang membutuhkan "kinerja" harus ditulis dalam hal lain.
David Moles