Tác động hiệu năng của việc sử dụng instanceof trong Java


314

Tôi đang làm việc trên một ứng dụng và một phương pháp thiết kế liên quan đến việc sử dụng instanceoftoán tử cực kỳ nặng . Mặc dù tôi biết rằng thiết kế OO thường cố gắng tránh sử dụng instanceof, đó là một câu chuyện khác và câu hỏi này hoàn toàn liên quan đến hiệu suất. Tôi đã tự hỏi nếu có bất kỳ tác động hiệu suất? Là chỉ nhanh như ==?

Ví dụ, tôi có một lớp cơ sở với 10 lớp con. Trong một hàm duy nhất có lớp cơ sở, tôi kiểm tra xem lớp đó có phải là một thể hiện của lớp con không và thực hiện một số thường trình.

Một trong những cách khác mà tôi nghĩ đến khi giải quyết nó là sử dụng nguyên hàm số nguyên "loại id" và sử dụng bitmask để biểu diễn các thể loại của các lớp con, và sau đó chỉ cần so sánh mặt nạ bit của các lớp con "id id" với một mặt nạ không đổi đại diện cho thể loại.

Được instanceofbằng cách nào đó được tối ưu hóa bởi JVM để được nhanh hơn mà? Tôi muốn gắn bó với Java nhưng hiệu suất của ứng dụng là rất quan trọng. Sẽ thật tuyệt nếu ai đó đã từng đi trên con đường này trước đây có thể đưa ra một số lời khuyên. Tôi đang quá cố chấp hay tập trung vào những điều sai trái để tối ưu hóa?


81
Tuy nhiên, tôi nghĩ rằng vấn đề của câu hỏi là đặt sang một bên câu hỏi về thực hành OO tốt nhất và kiểm tra hiệu suất.
Dave L.

3
@Dave L. Thông thường tôi sẽ đồng ý, nhưng OP có đề cập rằng anh ấy đang tìm kiếm một số kỹ thuật tối ưu hóa chung và anh ấy không chắc liệu vấn đề của mình có liên quan đến 'instanceof' hay không. Tôi nghĩ rằng ít nhất nên đề cập đến thiết kế 'chính xác' để anh ấy có thể xác định cả hai lựa chọn.
Lập trình viên ngoài vòng pháp luật

51
Ugh ... Tại sao tất cả các câu trả lời đều bỏ lỡ điểm của câu hỏi và cung cấp cùng một lời hùng biện Knuth cũ về tối ưu hóa? Câu hỏi của bạn là về việc liệu instanceof có chậm hơn đáng kể / đáng ngạc nhiên so với việc kiểm tra đối tượng lớp bằng == và tôi đã thấy rằng nó không phải.
gubby

3
Hiệu suất của instanceof và đúc là khá tốt. Tôi đã đăng một số thời gian trong Java7 xung quanh các cách tiếp cận khác nhau cho vấn đề ở đây: stackoverflow.com/questions/16320014/
mẹo

Câu trả lời:


268

Các trình biên dịch JVM / JIC hiện đại đã loại bỏ cú đánh hiệu năng của hầu hết các hoạt động "chậm" truyền thống, bao gồm thể hiện, xử lý ngoại lệ, phản xạ, v.v.

Như Donald Knuth đã viết, "Chúng ta nên quên đi những hiệu quả nhỏ, nói về 97% thời gian: tối ưu hóa sớm là gốc rễ của mọi tội lỗi." Hiệu suất của instanceof có lẽ sẽ không thành vấn đề, vì vậy đừng lãng phí thời gian của bạn để tìm ra cách giải quyết kỳ lạ cho đến khi bạn chắc chắn đó là vấn đề.


13
JVM / JIC hiện đại .. Vui lòng đề cập đến từ phiên bản java mà các tối ưu hóa này đã được đề cập?
Ravisha

138
Luôn luôn có ai đó, người đã trích dẫn Knuth khi hiệu suất là chủ đề ... Quên đi, Knuth cũng đã tuyên bố (trong cùng một bài viết) "Trong các ngành kỹ thuật thành lập, cải thiện 12%, dễ dàng đạt được, không bao giờ được coi là cận biên và tôi tin rằng cùng quan điểm nên chiếm ưu thế trong công nghệ phần mềm ", gần như tất cả công việc của ông là về hiệu quả của các thuật toán và ông đã viết các thuật toán lắp ráp để (liên alia) đạt được hiệu suất tốt hơn. Meh ...
kgadek

4
Một bên ở đây nhưng sẽ try { ObjT o = (ObjT)object } catch (e) { no not one of these }nhanh hơn chậm hơn ??
peterk

35
Nếu "đối tượng" là một thể hiện của ObjT, thì việc truyền tải nó nhanh hơn một chút so với thực hiện một thể hiện, nhưng sự khác biệt mà bài kiểm tra nhanh của tôi tìm thấy là 10-20ms trên 10.000.000 lần lặp. Tuy nhiên, nếu "đối tượng" không phải là ObjT, thì việc bắt ngoại lệ chậm hơn 3000 lần - hơn 31.000ms so với ~ 10ms đối với cá thể.
Steve

19
một lập luận mạnh mẽ như vậy mà không có bất kỳ "tài liệu tham khảo" nào, là hoàn toàn vô dụng vì chỉ cần quan điểm.
marcorossi

279

Tiếp cận

Tôi đã viết một chương trình điểm chuẩn để đánh giá các triển khai khác nhau:

  1. instanceof thực hiện (như tài liệu tham khảo)
  2. đối tượng được định hướng thông qua một lớp trừu tượng và @Overridemột phương thức thử nghiệm
  3. sử dụng một kiểu thực hiện riêng
  4. getClass() == _.class thực hiện

Tôi đã sử dụng jmh để chạy điểm chuẩn với 100 cuộc gọi khởi động, 1000 lần lặp được đo và với 10 nhánh. Vì vậy, mỗi tùy chọn được đo bằng 10 000 lần, phải mất 12:18:57 để chạy toàn bộ điểm chuẩn trên MacBook Pro của tôi với macOS 10.12.4 và Java 1.8. Điểm chuẩn đo thời gian trung bình của mỗi tùy chọn. Để biết thêm chi tiết, xem triển khai của tôi trên GitHub .

Để hoàn thiện: Có một phiên bản trước của câu trả lời này và điểm chuẩn của tôi .

Các kết quả

| Hoạt động | Thời gian chạy tính bằng nano giây trên mỗi thao tác | Liên quan đến thể hiện |
| ------------ | ------------------------------------ - | ------------------------ |
| NGAY LẬP TỨC | 39,598 ± 0,022 ns / op | 100,00% |
| NHẬN | 39,687 ± 0,021 ns / op | 100,22% |
| LOẠI | 46,295 ± 0,026 ns / op | 116,91% |
| Ôi | 48,078 ± 0,026 ns / op | 121,42% |

tl; dr

Trong Java 1.8 instanceoflà cách tiếp cận nhanh nhất, mặc dùgetClass() rất gần.


58
+0.(9)cho khoa học!

16
+ 0,1 khác từ tôi: D
Tobias Reich

14
@TobiasReich Vậy là chúng ta đã có +1.0(9). :)
Pavel

9
Tôi không nghĩ rằng biện pháp này có ý nghĩa gì cả. Mã đo lường sử dụng System.currentTimeMillis()trên một hoạt động không nhiều hơn một lệnh gọi phương thức đơn lẻ, điều này sẽ mang lại độ chính xác thấp. Sử dụng một khung điểm chuẩn như JMH thay thế!
Lii

6
Hoặc chỉ thực hiện thời gian của cả tỷ cuộc gọi thay vì mỗi cuộc gọi.
Truyền thuyết Bước sóng

74

Tôi vừa thực hiện một thử nghiệm đơn giản để xem hiệu năng của instanceOf so với lệnh gọi s.equals () đơn giản đến một đối tượng chuỗi chỉ bằng một chữ cái.

trong một vòng lặp 10.000.000, instanceOf đã cho tôi 63-96ms và chuỗi bằng cho tôi 106-230ms

Tôi đã sử dụng java jvm 6.

Vì vậy, trong thử nghiệm đơn giản của tôi là nhanh hơn để thực hiện một instanceOf thay vì so sánh một chuỗi ký tự.

bằng cách sử dụng .equals () thay vì chuỗi đã cho tôi kết quả tương tự, chỉ khi tôi sử dụng == i nhanh hơn instanceOf 20ms (trong vòng lặp 10.000.000)


4
Bạn có thể gửi mã ở đây không? Điêu đo thật tuyệt vơi!
Nhà giả kim

7
InstOf đã so sánh như thế nào với chức năng đa hình?
Chris

21
Tại sao bạn so sánh instanceof với String.equals ()? Nếu bạn muốn kiểm tra loại bạn phải truy cập object.getClass (). Bằng (Một số loại.)
marsbear

4
@marsbear equals()sẽ không cắt nó, vì phân lớp; bạn cần isAssignableFrom().
David Moles

1
@marsbear Đúng, nhưng đó không phải là thử nghiệm tốt hơn về những gì OP yêu cầu.
David Moles

20

Các mục sẽ xác định tác động hiệu suất là:

  1. Số lượng các lớp có thể mà toán tử instanceof có thể trả về true
  2. Việc phân phối dữ liệu của bạn - hầu hết các hoạt động thể hiện được giải quyết trong lần thử đầu tiên hoặc lần thứ hai? Bạn sẽ muốn đặt khả năng cao nhất của bạn để trả lại hoạt động thực sự đầu tiên.
  3. Môi trường triển khai. Chạy trên Sun Solaris VM khác biệt đáng kể so với Windows JVM của Sun. Solaris sẽ chạy ở chế độ 'máy chủ' theo mặc định, trong khi Windows sẽ chạy ở chế độ máy khách. Tối ưu hóa JIT trên Solaris, sẽ làm cho tất cả các phương thức truy cập có thể giống nhau.

Tôi đã tạo ra một microbenchmark cho bốn phương thức gửi khác nhau . Kết quả từ Solaris như sau, với số lượng nhỏ hơn sẽ nhanh hơn:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 

18

Trả lời câu hỏi cuối cùng của bạn: Trừ khi một người lập hồ sơ nói với bạn, rằng bạn dành thời gian vô lý trong một ví dụ: Có, bạn đang gây nghiện.

Trước khi tự hỏi về việc tối ưu hóa một cái gì đó không bao giờ cần phải được tối ưu hóa: Viết thuật toán của bạn theo cách dễ đọc nhất và chạy nó. Chạy nó, cho đến khi trình biên dịch jit có cơ hội tối ưu hóa chính nó. Nếu sau đó bạn gặp vấn đề với đoạn mã này, hãy sử dụng một trình lược tả để cho bạn biết, nơi để đạt được nhiều nhất và tối ưu hóa điều này.

Trong thời gian trình biên dịch tối ưu hóa cao, dự đoán của bạn về các nút cổ chai sẽ có thể hoàn toàn sai.

Và theo đúng tinh thần của câu trả lời này (điều mà tôi hoàn toàn tin tưởng): Tôi hoàn toàn không biết làm thế nào thể hiện và == liên quan một khi trình biên dịch jit có cơ hội tối ưu hóa nó.

Tôi quên: Không bao giờ đo lần chạy đầu tiên.


1
Nhưng áp phích ban đầu đề cập đến hiệu suất là rất quan trọng cho ứng dụng này, vì vậy không phải là không hợp lý để tối ưu hóa sớm trong tình huống đó. Nói cách khác, bạn sẽ không viết một trò chơi 3d bằng GWBasic và cuối cùng, hãy nói, hãy bắt đầu tối ưu hóa điều này, bước đầu tiên là chuyển nó sang c ++.
Truyền thuyết Bước sóng

GWBasic có thể là một khởi đầu tuyệt vời cho các trò chơi 3d, nếu có sẵn các thư viện thích hợp. Nhưng điều đó sang một bên (vì đó là một lập luận giả tạo): OP không yêu cầu viết lại hoàn toàn như là tối ưu hóa. Đó là về một cấu trúc duy nhất mà chúng ta thậm chí không biết liệu tác động có đáng kể hay không (ngay cả khi có cách thực hiện tốt hơn để làm điều tương tự trong phiên bản hiện tại của trình biên dịch ). Tôi chắc chắn đứng đằng sau c2.com/cgi/wiki?ProfileB BeforeOptimizing và câu trả lời của tôi. Tối ưu hóa sơ bộ là gốc rễ của mọi tội lỗi! Nó làm cho việc bảo trì khó khăn hơn - và bảo trì là khía cạnh đáng để tối ưu hóa
Olaf Kock

15

Tôi có cùng một câu hỏi, nhưng vì tôi không tìm thấy 'số liệu hiệu suất' cho trường hợp sử dụng tương tự như của tôi, tôi đã thực hiện thêm một số mã mẫu. Trên phần cứng của tôi và Java 6 & 7, sự khác biệt giữa thể hiện và chuyển đổi trên 10 triệu lần lặp là

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

Vì vậy, instanceof thực sự chậm hơn, đặc biệt là với số lượng lớn các câu lệnh if-if-if, tuy nhiên sự khác biệt sẽ không đáng kể trong ứng dụng thực tế.

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()));
    }
}

Kết quả nào là java 6 và java 7 là gì? Bạn đã xem lại điều này trong Java 8 chưa? Quan trọng hơn ở đây, bạn đang so sánh độ dài nếu ví dụ với những gì cần thiết là một tuyên bố trường hợp trên ints. Tôi nghĩ rằng chúng ta sẽ mong đợi một chuyển đổi int sẽ được làm sáng nhanh.
Azeroth2b

1
Tôi không thể nhớ chính xác những gì đã xảy ra 5 năm trước - Tôi nghĩ rằng cả Java 6 và Java 7 đều có kết quả tương tự nhau, đó là lý do tại sao chỉ có một kết quả được cung cấp (cung cấp 2 dòng cho độ phân cấp lớp khác nhau) ... và không , tôi đã không thử so sánh với Java 8. Toàn bộ mã kiểm tra được cung cấp - bạn có thể sao chép / dán nó và kiểm tra trong các môi trường bạn cần (lưu ý - hiện tại tôi sẽ sử dụng thử nghiệm JMH cho việc này).
Xtra

9

instanceof thực sự nhanh, chỉ mất một vài hướng dẫn CPU.

Rõ ràng, nếu một lớp Xkhông có lớp con được tải (JVM biết), instanceofcó thể được tối ưu hóa thành:

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

Chi phí chính chỉ là một đọc!

Nếu X không có lớp con được tải, cần thêm một vài lần đọc; chúng có khả năng nằm cùng vị trí nên chi phí tăng thêm cũng rất thấp.

Tin vui nhé mọi người!


2
có thể được tối ưu hóa hay được tối ưu hóa? nguồn nào?

@vaxquis có thể là jvm của nó cụ thể
RecursiveExceptionException

@itzJanuary thở dài bạn bị mất điểm của câu hỏi của tôi ở đây: mọi người đều biết trình biên dịch mà có thể tối ưu hóa foo- nhưng được foothực hiện tối ưu hóa bởi Oracle javac / VM - hoặc là nó chỉ có thể là nó sẽ làm điều đó trong tương lai? Ngoài ra, tôi đã hỏi người trả lời rằng anh ta có bất kỳ nguồn sao lưu nào không (có thể là tài liệu, mã nguồn, blog dev) tài liệu rằng nó thực sự có thể được tối ưu hóa hoặc được tối ưu hóa ? Không có nó, câu trả lời này chỉ là một số suy nghĩ ngẫu nhiên về những gì trình biên dịch thể làm.

@vaxquis Bạn chưa bao giờ đề cập đến Hotspot VM nhưng trong trường hợp đó tôi không biết nó có được "tối ưu hóa" hay không.
RecursiveExceptionException

1
Gần đây đọc rằng JIT (JVM 8) sẽ tối ưu hóa một trang web cuộc gọi cho 1 hoặc 2 loại bằng các cuộc gọi trực tiếp, nhưng sẽ hoàn nguyên về vtable nếu gặp hơn hai loại thực tế. Vì vậy, chỉ có hai loại cụ thể đi qua một trang web cuộc gọi trong thời gian chạy thể hiện lợi thế về hiệu suất.
simon.watts

5

Instanceof rất nhanh. Nó sôi sùng sục xuống một mã byte được sử dụng để so sánh tham chiếu lớp. Hãy thử một vài triệu ví dụ trong một vòng lặp và tự mình xem.


5

instanceof có lẽ sẽ tốn kém hơn so với một đơn giản bằng trong hầu hết các triển khai trong thế giới thực (nghĩa là những cái mà thực sự cần thiết, và bạn không thể giải quyết nó bằng cách ghi đè một phương pháp phổ biến, như mọi sách giáo khoa mới bắt đầu cũng như Demian ở trên đề nghị).

Tại sao vậy? Bởi vì điều có thể sẽ xảy ra là bạn có một số giao diện, cung cấp một số chức năng (giả sử, giao diện x, y và z) và một số đối tượng để thao tác có thể (hoặc không) thực hiện một trong những giao diện đó ... nhưng không trực tiếp. Ví dụ, tôi có:

w kéo dài x

Một dụng cụ w

B kéo dài A

C mở rộng B, thực hiện y

D mở rộng C, thực hiện z

Giả sử tôi đang xử lý một thể hiện của D, đối tượng d. Việc tính toán (d instanceof x) yêu cầu lấy d.getClass (), lặp qua các giao diện mà nó thực hiện để biết liệu có phải là == với x hay không và nếu không làm như vậy một lần nữa đệ quy cho tất cả tổ tiên của chúng ... Trong trường hợp của chúng tôi, nếu bạn thực hiện khám phá đầu tiên về cây đó, sẽ có ít nhất 8 phép so sánh, giả sử y và z không mở rộng bất cứ điều gì ...

Độ phức tạp của cây phái sinh trong thế giới thực có khả năng cao hơn. Trong một số trường hợp, JIT có thể tối ưu hóa phần lớn trong số đó, nếu nó có thể giải quyết trước d, trong mọi trường hợp có thể, một trường hợp của một cái gì đó kéo dài x. Trên thực tế, tuy nhiên, bạn sẽ đi qua cây đó hầu hết thời gian.

Nếu điều đó trở thành một vấn đề, tôi sẽ đề nghị sử dụng bản đồ xử lý thay vào đó, liên kết lớp cụ thể của đối tượng với một bao đóng thực hiện việc xử lý. Nó loại bỏ giai đoạn truyền qua cây theo hướng ánh xạ trực tiếp. Tuy nhiên, hãy cẩn thận nếu bạn đã đặt trình xử lý cho C. class, đối tượng của tôi ở trên sẽ không được nhận ra.

đây là 2 xu của tôi, tôi hy vọng họ sẽ giúp ...


5

instanceof rất hiệu quả, do đó hiệu suất của bạn khó có thể bị ảnh hưởng. Tuy nhiên, sử dụng nhiều thể hiện cho thấy một vấn đề thiết kế.

Nếu bạn có thể sử dụng xClass == String. Class, việc này sẽ nhanh hơn. Lưu ý: bạn không cần instanceof cho các lớp cuối cùng.


1
Btw bạn có ý gì bởi "không cần instanceof cho các lớp cuối cùng"?
Pacerier

Một lớp cuối cùng không thể có lớp phụ. Trong trường hợp x.getClass() == Class.classnày giống nhưx instanceof Class
Peter Lawrey

Thật tuyệt, giả sử rằng x không phải là null, bạn thích cái nào hơn?
Pacerier

Điểm tốt. Nó sẽ phụ thuộc vào việc tôi dự kiến xnulltôi. (Hoặc bất cứ điều gì rõ ràng hơn)
Peter Lawrey

Hmm Tôi mới nhận ra rằng chúng ta cũng có thể sử dụng java.lang. Class.isAssignableFrom, bạn có biết nếu từ khóa instanceof bên trong sử dụng các hàm như thế này không?
Pacerier

4

Nói chung, lý do tại sao toán tử "instanceof" được tán thành trong trường hợp như vậy (trong đó thể hiện đang kiểm tra các lớp con của lớp cơ sở này) là vì những gì bạn nên làm là chuyển các hoạt động sang một phương thức và ghi đè lên nó cho phù hợp các lớp con. Chẳng hạn, nếu bạn có:

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

Bạn có thể thay thế bằng

o.doEverything();

và sau đó thực hiện "doEverything ()" trong Class1 gọi "do This ()" và trong Class2 gọi "doThat ()", v.v.


11
Nhưng đôi khi bạn không thể. Nếu bạn đang triển khai một giao diện mà bạn đang sử dụng Object và bạn cần cho biết đó là loại nào, thì instanceof thực sự là lựa chọn duy nhất. Bạn có thể thử truyền, nhưng instanceof thường sạch hơn.
Herms

4

'instanceof' thực sự là một toán tử, như + hoặc - và tôi tin rằng nó có hướng dẫn mã byte JVM riêng. Nó sẽ rất nhanh.

Tôi không nên rằng nếu bạn có một công tắc nơi bạn đang kiểm tra nếu một đối tượng là một thể hiện của một lớp con nào đó, thì thiết kế của bạn có thể cần phải được làm lại. Xem xét đẩy hành vi cụ thể của lớp con xuống chính các lớp con.


4

Demian và Paul đề cập đến một điểm tốt; Tuy nhiên , vị trí của mã để thực thi thực sự phụ thuộc vào cách bạn muốn sử dụng dữ liệu ...

Tôi là một fan hâm mộ lớn của các đối tượng dữ liệu nhỏ có thể được sử dụng theo nhiều cách. Nếu bạn làm theo cách tiếp cận ghi đè (đa hình), các đối tượng của bạn chỉ có thể được sử dụng "một chiều".

Đây là nơi các mẫu đến ...

Bạn có thể sử dụng công văn kép (như trong mẫu khách truy cập) để yêu cầu mỗi đối tượng "gọi bạn" đi qua - điều này sẽ giải quyết loại đối tượng. Tuy nhiên (một lần nữa) bạn sẽ cần một lớp có thể "làm công cụ" với tất cả các kiểu con có thể.

Tôi thích sử dụng một mẫu chiến lược, nơi bạn có thể đăng ký chiến lược cho từng loại phụ bạn muốn xử lý. Một cái gì đó như sau. Lưu ý rằng điều này chỉ giúp cho các loại khớp chính xác, nhưng có lợi thế là nó có thể mở rộng - những người đóng góp bên thứ ba có thể thêm các loại và trình xử lý của riêng họ. (Điều này tốt cho các khung động như OSGi, nơi có thể thêm các gói mới)

Hy vọng rằng điều này sẽ truyền cảm hứng cho một số ý tưởng khác ...

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);
    }
}

4

Tôi viết một bài kiểm tra hiệu suất dựa trên jmh-java-điểm chuẩn-archetype: 2.21. JDK là openjdk và phiên bản là 1.8.0_212. Máy thử nghiệm là mac pro. Kết quả kiểm tra là:

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

Kết quả cho thấy: getClass tốt hơn instanceOf, trái với thử nghiệm khác. Tuy nhiên, tôi không biết tại sao.

Mã kiểm tra dưới đây:

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();
}
}

Nếu tôi suy đoán, thì trường hợp nào thì phức tạp hơn. Kiểm tra getClass () == sẽ thực hiện kiểm tra chính xác 1: 1, trong đó instanceof kiểm tra một cấu trúc phân cấp, ví dụ myHashset instanceof Collection sẽ vượt qua, nhưng myHashSet.getClass () == Collection. Class sẽ không. Về cơ bản, chúng không phải là các hoạt động tương đương, vì vậy tôi không quá ngạc nhiên khi hiệu suất cũng khác nhau.
AMTerp

3

Thật khó để nói làm thế nào một JVM nhất định thực hiện các thể hiện của, nhưng trong hầu hết các trường hợp, các Đối tượng có thể so sánh với các cấu trúc và các lớp cũng như vậy và mọi cấu trúc đối tượng đều có một con trỏ tới cấu trúc lớp mà nó là một thể hiện của. Vì vậy, thực sự là ví dụ cho

if (o instanceof java.lang.String)

có thể nhanh như mã C sau

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

giả sử một trình biên dịch JIT được đặt ra và thực hiện một công việc tốt.

Xem xét rằng điều này chỉ truy cập vào một con trỏ, lấy một con trỏ ở một độ lệch nhất định mà con trỏ trỏ tới và so sánh nó với một con trỏ khác (về cơ bản giống như kiểm tra các số 32 bit bằng nhau), tôi thực sự có thể nói rằng hoạt động có thể thực sự rất nhanh

Mặc dù vậy, nó không phải phụ thuộc rất nhiều vào JVM. Tuy nhiên, nếu điều này hóa ra là hoạt động thắt cổ chai trong mã của bạn, thì tôi cho rằng việc triển khai JVM khá kém. Ngay cả một trình duyệt không có trình biên dịch JIT và chỉ có mã thông dịch nên có thể thực hiện kiểm tra thể hiện trong hầu như không có thời gian.


1
Nó không phải tìm ra nếu o kế thừa từ java.lang.String?
Thế chiến.

1
Đó là lý do tại sao tôi nói nó "có thể" nhanh như vậy. Trong thực tế, nó thực hiện một vòng lặp, đầu tiên kiểm tra iAmInstanceOf so với lớp được đề cập, sau đó nó đi lên cây thừa kế của o và lặp lại kiểm tra này cho mọi siêu hạng của o (vì vậy nó có thể phải chạy vòng lặp này một vài lần cho một trận đấu)
Mecki

3

Tôi sẽ lấy lại cho bạn về hiệu suất của thể hiện. Nhưng một cách để tránh vấn đề (hoặc thiếu nó) hoàn toàn sẽ là tạo một giao diện cha cho tất cả các lớp con mà bạn cần thực hiện. Giao diện sẽ là một tập hợp tất cả các phương thức trong các lớp con mà bạn cần thực hiện kiểm tra thể hiện. Khi một phương thức không áp dụng cho một lớp con cụ thể, chỉ cần cung cấp một triển khai giả của phương thức này. Nếu tôi không hiểu sai về vấn đề này, thì đây là cách tôi đã giải quyết vấn đề trong quá khứ.


2

InstanceOf là một cảnh báo về thiết kế hướng đối tượng kém.

Các JVM hiện tại có nghĩa là instanceOf không phải là vấn đề đáng lo ngại về hiệu năng. Nếu bạn đang thấy mình sử dụng nó rất nhiều, đặc biệt là cho chức năng cốt lõi, có lẽ đã đến lúc xem xét thiết kế. Hiệu suất (và tính đơn giản / khả năng bảo trì) của việc tái cấu trúc thành một thiết kế tốt hơn sẽ vượt xa bất kỳ chu kỳ bộ xử lý thực tế nào dành cho cá thể thực tế cuộc gọi .

Để đưa ra một ví dụ lập trình đơn giản rất nhỏ.

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

Là một kiến ​​trúc kém, một lựa chọn tốt hơn sẽ có một sốOOject là lớp cha của hai lớp con trong đó mỗi lớp con ghi đè một phương thức (doS Something) để mã sẽ trông như sau:

Someobject.doSomething();

61
Tôi biết điều đó. Đó không phải là những gì tôi yêu cầu.
Josh

Không chắc chắn về việc có nên bỏ phiếu này hay không vì đây là một điểm tốt, nhưng không trả lời câu hỏi được hỏi ...
jklp

7
Tôi nghĩ rằng ví dụ mã thực sự là một ví dụ rất tệ: Bạn không thể mở rộng lớp Double và bạn cũng không thể lấy được Double từ một số lớp khác. Nếu bạn đã sử dụng các lớp khác cho ví dụ, nó sẽ ổn thôi.
Lena Schimmel

6
Ngoài ra nếu các lớp con của someObject là các đối tượng giá trị, thì bạn không muốn đặt logic vào chúng. Ví dụ: Pie và Roast có thể không phải là vị trí chính xác cho logic putInOven () và putInMouth ().
sk.

bánh tự nấu và nướng sẽ là tuyệt vời mặc dù
binboavetonik

2

Trong phiên bản Java hiện đại, toán tử instanceof nhanh hơn như một cuộc gọi phương thức đơn giản. Điều này có nghĩa là:

if(a instanceof AnyObject){
}

nhanh hơn là:

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

Một điều nữa là nếu bạn cần xếp tầng nhiều thể hiện. Sau đó, một công tắc chỉ gọi một lần getType () sẽ nhanh hơn.


1

Nếu tốc độ là mục tiêu duy nhất của bạn thì sử dụng hằng số int để xác định các lớp con dường như sẽ cạo một phần nghìn giây của thời gian

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;
}

thiết kế OO khủng khiếp, nhưng nếu phân tích hiệu suất của bạn chỉ ra đây là nơi bạn đang tắc nghẽn thì có thể. Trong mã của tôi, mã công văn chiếm 10% tổng thời gian thực hiện và điều này có thể góp phần cải thiện tổng tốc độ 1%.


0

Bạn nên đo / hồ sơ nếu đó thực sự là một vấn đề hiệu suất trong dự án của bạn. Nếu tôi muốn giới thiệu thiết kế lại - nếu có thể. Tôi khá chắc chắn rằng bạn không thể đánh bại việc triển khai riêng của nền tảng (viết bằng C). Bạn cũng nên xem xét việc thừa kế nhiều trong trường hợp này.

Bạn nên nói thêm về vấn đề, có thể bạn có thể sử dụng một cửa hàng liên kết, ví dụ: Bản đồ <Class, Object> nếu bạn chỉ quan tâm đến các loại cụ thể.


0

Đối với lưu ý của Peter Lawrey rằng bạn không cần thể hiện cho các lớp cuối cùng và chỉ có thể sử dụng một đẳng thức tham chiếu, hãy cẩn thận! Mặc dù các lớp cuối cùng không thể được mở rộng, chúng không được đảm bảo để được tải bởi cùng một trình nạp lớp. Chỉ sử dụng x.getClass () == someFinal. Class hoặc ilk của nó nếu bạn hoàn toàn tích cực rằng chỉ có một trình nạp lớp đang chơi cho phần mã đó.


4
Nếu một lớp được tải bởi một trình nạp lớp khác, tôi không nghĩ instanceof sẽ khớp.
Peter Lawrey

0

Tôi cũng thích một cách tiếp cận enum, nhưng tôi sẽ sử dụng một lớp cơ sở trừu tượng để buộc các lớp con thực hiện getType()phương thức này.

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;
    }
  }
}

0

Tôi nghĩ rằng có thể đáng để gửi một ví dụ ngược lại với sự đồng thuận chung trên trang này rằng "instanceof" không đủ đắt để lo lắng. Tôi thấy tôi có một số mã trong một vòng lặp bên trong mà (trong một số nỗ lực lịch sử tối ưu hóa) đã làm

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

trong đó việc gọi head () trên SingleItem trả về giá trị không đổi. Thay thế mã bằng

seq = seq.head();

giúp tôi tăng tốc từ 269ms lên 169ms, mặc dù thực tế là có một số điều khá nặng nề xảy ra trong vòng lặp, như chuyển đổi chuỗi thành gấp đôi. Tất nhiên có thể là việc tăng tốc là do loại bỏ nhánh có điều kiện hơn là loại bỏ chính toán tử instanceof; nhưng tôi nghĩ nó đáng được đề cập.


Điều này có thể là do ifchính nó. Nếu phân phối của trues và falses gần với chẵn, việc thực hiện đầu cơ trở nên vô dụng, dẫn đến độ trễ đáng kể.
Dmytro

-4

Bạn đang tập trung vào những điều sai trái. Sự khác biệt giữa instanceof và bất kỳ phương pháp nào khác để kiểm tra cùng một thứ có lẽ sẽ không thể đo lường được. Nếu hiệu suất là quan trọng thì Java có thể là ngôn ngữ sai. Lý do chính là bạn không thể kiểm soát khi VM quyết định muốn thu gom rác, điều này có thể khiến CPU đạt 100% trong vài giây trong một chương trình lớn (MagicDraw 10 rất phù hợp với điều đó). Trừ khi bạn kiểm soát mọi máy tính, chương trình này sẽ chạy trên bạn, bạn không thể đảm bảo phiên bản JVM nào sẽ được bật và nhiều phiên bản cũ hơn có vấn đề lớn về tốc độ. Nếu đó là một ứng dụng nhỏ, bạn có thể ổn với Java, nhưng nếu bạn liên tục đọc và loại bỏ dữ liệu thì bạn sẽ nhận thấy khi GC khởi động.


7
Điều đó ít đúng với các thuật toán thu gom rác java hiện đại hơn trước đây. Ngay cả các thuật toán đơn giản nhất cũng không quan tâm đến việc bạn loại bỏ bao nhiêu bộ nhớ chỉ sau khi bạn sử dụng - chúng chỉ quan tâm số lượng được giữ lại trong các bộ sưu tập thế hệ trẻ.
Bill Michell

3
Tuyệt vời, ngoại trừ tôi đang sử dụng JVM gần đây nhất và máy tính của tôi vẫn thu thập dữ liệu khi GC chạy. Trên máy chủ ram lõi kép, 3 GB. Java không phải là ngôn ngữ để sử dụng nếu hiệu suất thực sự quan trọng.
tloach

@David: Bạn không cần thời gian thực để có vấn đề khi ứng dụng của bạn biến mất trong một khoảng thời gian. Một điều thú vị mà tôi đã gặp là một ứng dụng java được kết nối với luồng TCP đã chết khi GC chạy vì nó không đóng luồng trước và không thể xử lý tình trạng quá tải lưu lượng mạng khi nó quay lại - nó sẽ ngay lập tức đi vào một vòng lặp nơi GC chạy, khi ứng dụng tiếp tục, nó cố gắng lướt qua một loạt dữ liệu, khiến nó hết bộ nhớ, điều này đã kích hoạt GC, v.v. Java rất tốt cho rất nhiều nhiệm vụ, nhưng không phải là nhiệm vụ trong đó hiệu suất mạnh mẽ là một yêu cầu.
tloach

6
@tloach âm thanh với tôi như thiết kế ứng dụng xấu. bạn nói về "hiệu suất" như thể nó là một chiều. Tôi đã làm việc với (và trên) rất nhiều ứng dụng java, ví dụ, người thực hiện trong việc cung cấp phân tích thống kê tương tác linh hoạt và trực quan hóa các tập dữ liệu rất lớn hoặc người thực hiện xử lý khối lượng giao dịch rất lớn rất nhanh. "hiệu suất" không chỉ là một điều, và thực tế là ai đó có thể viết một ứng dụng quản lý bộ nhớ kém và để cho GC có được theo cách riêng của nó không có nghĩa là bất cứ điều gì đòi hỏi "hiệu suất" nên được viết bằng thứ khác.
David Moles
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.