2010-01-31 41 views
84

Có bất kỳ chi phí nào khi chúng tôi truyền các đối tượng của loại này sang loại khác không? Hoặc trình biên dịch chỉ giải quyết mọi thứ và không mất chi phí khi chạy?Java đúc có giới thiệu chi phí không? Tại sao?

Đây có phải là điều chung hay không?

Ví dụ: giả sử chúng ta có một mảng đối tượng [], trong đó mỗi phần tử có thể có một loại khác nhau. Nhưng chúng ta luôn biết chắc chắn rằng, phần tử 0 là một phần tử Double, phần tử 1 là một String. (Tôi biết đây là một thiết kế sai, nhưng chúng ta hãy giả sử tôi phải làm điều này.)

Thông tin kiểu Java vẫn giữ nguyên trong thời gian chạy? Hoặc tất cả mọi thứ bị lãng quên sau khi biên dịch, và nếu chúng ta làm (Double) các yếu tố [0], chúng ta sẽ chỉ theo con trỏ và giải thích 8 byte đó là gấp đôi, bất kể cái gì?

Tôi không rõ về cách thực hiện các loại trong Java. Nếu bạn có bất kỳ lời khuyên nào về sách hoặc bài viết thì cũng cảm ơn.

+0

Hiệu suất của instanceof và truyền 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: http://stackoverflow.com/questions/16320014/java-optimization-nitpick-is-it-faster-to-cast-something-and-let-it- throw-excep/28858680 # 28858680 – Wheezil

+0

Câu hỏi khác này có câu trả lời rất tốt http://stackoverflow.com/questions/16741323/casting-performance-in-different-levels-when-casting-down – user454322

Trả lời

63

Có 2 loại đúc:

Implicit đúc, khi bạn cast từ một loại một loại rộng hơn, được thực hiện tự động và không có chi phí:

String s = "Cast"; 
Object o = s; // implicit casting 

Explicit truyền, khi bạn chuyển từ loại rộng hơn sang loại hẹp hơn. Đối với trường hợp này, bạn phải rõ ràng sử dụng đúc như thế:

Object o = someObject; 
String s = (String) o; // explicit casting 

Trong trường hợp thứ hai này, có overhead là trong thời gian chạy, bởi vì hai loại phải được kiểm tra và trong trường hợp đó đúc là không khả thi, JVM phải ném một ClassCastException.

Taken từ JavaWorld: The cost of casting

Đúc được sử dụng để chuyển đổi giữa loại - giữa các loại tài liệu tham khảo trong Đặc biệt, đối với loại đúc hoạt động mà chúng ta đang quan tâm đây.

upCast hoạt động (còn gọi là chuyển đổi mở rộng trong Java Language Specification) chuyển đổi một tài liệu tham khảo lớp con đến một tài liệu tham khảo lớp tổ tiên . Thao tác này đúc thường là tự động, vì nó luôn an toàn và có thể được triển khai trực tiếp bởi trình biên dịch.

downCast hoạt động (còn gọi là chuyển đổi thu hẹp trong Java Language Specification) chuyển đổi một tài liệu tham khảo lớp tổ tiên để một lớp con tham khảo. Thao tác đúc này tạo phí trên không thực thi, vì Java yêu cầu phải chọn mẫu thử tại thời gian chạy để đảm bảo rằng nó hợp lệ. Nếu đối tượng được tham chiếu không phải là trường hợp của loại mục tiêu cho dàn diễn viên hoặc lớp con thuộc loại đó, không được phép sử dụng và phải ném java.lang.ClassCastException.

+79

Bài viết JavaWorld đó là trên 10 năm cũ, vì vậy tôi sẽ đưa ra bất kỳ tuyên bố nó làm cho về hiệu suất với một hạt rất lớn của muối tốt nhất của bạn. – skaffman

+0

@skaffman, Trên thực tế, tôi sẽ đưa ra bất kỳ tuyên bố nào mà nó tạo ra (bất kể việc thực hiện không) với một hạt muối. – Pacerier

+0

sẽ là trường hợp tương tự, nếu tôi không gán đối tượng được truyền vào tham chiếu và chỉ cần gọi phương thức trên đó? như '((String) o) .someMethodOfCastedClass()' –

7

Ví dụ: giả sử chúng ta có một mảng đối tượng [], trong đó mỗi phần tử có thể có một loại khác. Nhưng chúng ta luôn biết chắc chắn rằng, phần tử 0 là một phần tử Double, phần tử 1 là một String. (Tôi biết đây là một thiết kế sai, nhưng hãy giả sử tôi phải làm điều này.)

Trình biên dịch không lưu ý các loại phần tử riêng lẻ của một mảng. Nó chỉ đơn giản là kiểm tra xem kiểu của mỗi biểu thức phần tử có thể gán cho kiểu phần tử mảng hay không.

Thông tin về loại Java vẫn được lưu giữ trong thời gian chạy? Hoặc tất cả mọi thứ bị lãng quên sau khi biên dịch, và nếu chúng ta làm (Double) các yếu tố [0], chúng ta sẽ chỉ theo con trỏ và giải thích 8 byte đó là gấp đôi, bất kể cái gì?

Một số thông tin được lưu giữ trong thời gian chạy, nhưng không phải là loại tĩnh của các yếu tố riêng lẻ. Bạn có thể nói điều này bằng cách xem định dạng tệp lớp. Về mặt lý thuyết có thể là trình biên dịch JIT có thể sử dụng "phân tích thoát" để loại bỏ các kiểm tra kiểu không cần thiết trong một số bài tập. Tuy nhiên, làm điều này đến mức độ mà bạn đang đề xuất sẽ vượt quá giới hạn của tối ưu hóa thực tế. Phần thưởng của việc phân tích các loại yếu tố riêng lẻ sẽ quá nhỏ.

Bên cạnh đó, mọi người không nên viết mã ứng dụng như vậy.

+0

Còn về nguyên thủy? '(float) Math.toDegrees (theta)' Sẽ có một chi phí đáng kể ở đây? –

+1

Có một chi phí cho một số phôi nguyên thủy. Cho dù đó là đáng kể phụ thuộc vào bối cảnh. –

5

Hướng dẫn mã byte để thực hiện truyền trong thời gian chạy được gọi là checkcast. Bạn có thể tháo rời mã Java bằng cách sử dụng javap để xem hướng dẫn nào được tạo.

Đối với mảng, Java giữ thông tin loại khi chạy. Hầu hết thời gian, trình biên dịch sẽ bắt lỗi loại cho bạn, nhưng có trường hợp bạn sẽ chạy vào một số ArrayStoreException khi cố gắng lưu trữ một đối tượng trong một mảng, nhưng loại không khớp (và trình biên dịch không bắt được nó). Các Java language spec đưa ra ví dụ sau đây:

class Point { int x, y; } 
class ColoredPoint extends Point { int color; } 
class Test { 
    public static void main(String[] args) { 
     ColoredPoint[] cpa = new ColoredPoint[10]; 
     Point[] pa = cpa; 
     System.out.println(pa[1] == null); 
     try { 
      pa[0] = new Point(); 
     } catch (ArrayStoreException e) { 
      System.out.println(e); 
     } 
    } 
} 

Point[] pa = cpa là hợp lệ kể từ ColoredPoint là một lớp con của Point, nhưng pa[0] = new Point() là không hợp lệ.

Điều này trái ngược với các loại chung chung, nơi không có thông tin loại được lưu giữ trong thời gian chạy. Trình biên dịch chèn checkcast hướng dẫn khi cần thiết.

Sự khác biệt này trong việc nhập các loại và mảng chung làm cho nó thường không phù hợp để trộn mảng và loại chung.

37

Đối với việc thực hiện hợp lý của Java:

Mỗi đối tượng có một tiêu đề chứa, trong số những thứ khác, một con trỏ đến kiểu thời gian chạy (ví dụ Double hoặc String, nhưng nó không bao giờ có thể là CharSequence hoặc AbstractList).Giả sử trình biên dịch thời gian chạy (thường là HotSpot trong trường hợp của Sun) không thể xác định kiểu tĩnh một số kiểm tra cần được thực hiện bằng mã máy được tạo ra.

Trước tiên, cần phải đọc con trỏ đó đến loại thời gian chạy. Điều này là cần thiết để gọi một phương pháp ảo trong một tình huống tương tự anyway.

Để truyền sang loại lớp, bạn biết chính xác số lượng siêu lớp cho đến khi bạn nhấn java.lang.Object, do đó loại có thể được đọc ở mức chênh lệch không đổi từ con trỏ loại (thực ra là 8 điểm đầu tiên trong HotSpot). Một lần nữa điều này tương tự như việc đọc một con trỏ phương thức cho một phương thức ảo.

Sau đó, giá trị đọc chỉ cần so sánh với loại tĩnh dự kiến ​​của dàn diễn viên. Tùy thuộc vào kiến ​​trúc bộ lệnh, một lệnh khác sẽ cần phải phân nhánh (hoặc lỗi) trên một nhánh không chính xác. Các ISA như ARM 32 bit có hướng dẫn có điều kiện và có thể có con đường buồn đi qua con đường hạnh phúc.

Giao diện khó khăn hơn do nhiều lần kế thừa giao diện. Nói chung hai phôi cuối cùng cho các giao diện được lưu trữ trong kiểu thời gian chạy. Trong những ngày đầu tiên (hơn một thập kỷ trước), các giao diện hơi chậm, nhưng điều đó không còn liên quan nữa.

Hy vọng rằng bạn có thể thấy rằng loại điều này phần lớn không liên quan đến hiệu suất. Mã nguồn của bạn quan trọng hơn. Xét về hiệu suất, cú đánh lớn nhất trong kịch bản của bạn là trách nhiệm nhớ cache từ việc truy tìm con trỏ đối tượng trên toàn bộ địa điểm (tất nhiên thông tin kiểu sẽ là phổ biến).

+1

thú vị - vậy điều này có nghĩa là đối với các lớp không giao diện nếu tôi viết lớp con Superclass sc = (Superclass); rằng trình biên dịch (jit ie: load time) sẽ "tĩnh" đặt vào phần bù từ Object trong mỗi lớp Superclass và Subclass trong tiêu đề "Class" của chúng và sau đó thông qua một add + so sánh đơn giản có thể giải quyết mọi thứ? - đó là tốt đẹp và nhanh chóng :) Đối với giao diện tôi sẽ giả sử không tồi tệ hơn một hashtable nhỏ hoặc btree? – peterk

+0

@peterk Để truyền giữa các lớp, cả địa chỉ đối tượng và "vtbl" (bảng con trỏ phương pháp, cộng với bảng phân cấp lớp, bộ đệm giao diện, v.v.) không thay đổi. Vì vậy, các [lớp] đúc kiểm tra các loại, và nếu nó phù hợp với không có gì khác đã xảy ra. –

1

Về lý thuyết, có phí giới thiệu. Tuy nhiên, các JVM hiện đại rất thông minh. Mỗi lần thực hiện khác nhau, nhưng không phải là không hợp lý khi cho rằng có thể tồn tại một triển khai mà JIT đã tối ưu hóa việc kiểm tra truyền khi nó có thể đảm bảo rằng sẽ không bao giờ có xung đột. Đối với những JVM cụ thể nào cung cấp, tôi không thể cho bạn biết. Tôi phải thừa nhận rằng tôi muốn biết chi tiết về việc tối ưu hóa JIT, nhưng đây là những kỹ sư của JVM phải lo lắng.

Đạo đức của câu chuyện là viết mã dễ hiểu trước. Nếu bạn đang gặp sự cố, hồ sơ và xác định sự cố của mình. Tỷ lệ cược tốt là sẽ không do đúc. Không bao giờ hy sinh mã an toàn, sạch sẽ nhằm cố gắng tối ưu hóa nó KHI BẠN CẦN BIẾT.

Các vấn đề liên quan