2011-02-10 35 views
13

Trích đoạn từ sổ cầu thang:Các lớp trừu tượng trong Scala có thực sự hoạt động tốt hơn các đặc điểm không?

Nếu hiệu quả là rất quan trọng, hãy hướng tới sử dụng lớp học. Hầu hết Java runtimes thực hiện một lời gọi phương thức ảo của một thành viên lớp một thao tác nhanh hơn hơn là một lời gọi phương thức giao diện. Các đặc điểm được biên dịch thành các giao diện và do đó có thể trả một khoản chi phí hiệu năng nhỏ. Tuy nhiên, bạn nên chỉ thực hiện lựa chọn này nếu bạn biết rằng đặc điểm được đề cập cấu thành một nút cổ chai hiệu suất và có bằng chứng cho thấy sử dụng lớp học thay vì thực tế giải quyết vấn đề.

Tôi đã viết một số mã đơn giản để xem điều gì thực sự xảy ra đằng sau hậu trường. Và tôi đã thông báo invokevirtual đang được sử dụng trong trường hợp của lớp trừu tượng và invokeinterface trong trường hợp giao diện. Nhưng không có vấn đề gì loại mã tôi đã viết chúng luôn luôn thực hiện như nhau. Tôi sử dụng HotSpot 1.6.0_18 ở chế độ máy chủ.

JIT có thực hiện tối ưu hóa công việc tuyệt vời như vậy không? Có ai có mã mẫu chứng minh khiếu nại từ sách về số invokevirutal là hoạt động nhanh hơn không?

+0

Nếu bạn đang suy nghĩ nghiêm túc về loại tối ưu hóa cấp thấp đó, bạn đang sử dụng ngôn ngữ lập trình sai. – Raphael

+4

@Raphael Nếu bạn đang sử dụng Scala trên hệ thống Android hoặc tương tự, bạn có thể cần chú ý đến tất cả những thứ mà bạn thường không nghĩ đến hai lần. – wheaties

+4

Đây không phải là tôi đang cố gắng tối ưu hóa mọi thứ. Đó là một bài tập học tập và thỏa mãn sự tò mò của riêng tôi. Tôi đánh giá cao bình luận của bạn @Raphael mặc dù. –

Trả lời

7

Nếu HotSpot thông báo rằng tất cả các trường hợp tại trang web gọi cùng loại, nó có thể sử dụng cuộc gọi phương thức đơn hình và cả phương pháp ảo và giao diện đều được tối ưu hóa theo cùng một cách. Các tài liệu PerformanceTechniquesVirtualCalls không phân biệt giữa các phương thức ảo và giao diện.

Nhưng trong trường hợp không phải là đa hình chung có thể có một số khác biệt. Tài liệu InterfaceCalls cho biết:

Không có sơ đồ tiền tố đơn giản nào trong đó các phương thức của giao diện được hiển thị ở vị trí cố định trong mỗi lớp triển khai giao diện đó. Thay vào đó, trong trường hợp chung (không đơn lẻ), một thường trình sơ khai được mã hóa phải lấy một danh sách các giao diện được thực hiện từ klassOop của người nhận và đi bộ danh sách đó tìm kiếm giao diện đích hiện tại.

Nó cũng khẳng định rằng trường hợp monomorphic là như nhau cho cả hai:

Gần như tối ưu hóa tương tự áp dụng để giao tiếp cuộc gọi khi cuộc gọi ảo. Giống như với các cuộc gọi ảo, hầu hết các cuộc gọi giao diện đều là đơn hình và do đó có thể được hiển thị dưới dạng cuộc gọi trực tiếp với séc giá rẻ.

Các JVM khác có thể có các tối ưu hóa khác nhau.

Bạn có thể thử điểm chuẩn vi mô (if you know how) gọi phương thức trên nhiều lớp thực hiện cùng một giao diện và trên nhiều lớp mở rộng cùng một lớp trừu tượng. Bằng cách đó, bạn có thể buộc JVM sử dụng các cuộc gọi phương thức không phải đơn phương. (Mặc dù trong cuộc sống thực, bất kỳ sự khác biệt nào cũng có thể không quan trọng, vì hầu hết các trang web cuộc gọi đều là đơn hình.)

0

Một trích dẫn từ Inside the Java Virtual Machine(Hướng dẫn Invocation và Speed):

Khi Java Virtual Machine gặp một invokevirtual hướng dẫn và giải quyết các tham khảo tượng trưng cho một tham chiếu trực tiếp đến một phương pháp thể hiện, tham chiếu trực tiếp có khả năng được bù vào một phương thức bảng. Từ thời điểm đó trở đi, có thể sử dụng mức chênh lệch tương tự. Đối với một lệnh invokeinterface Tuy nhiên, máy ảo sẽ phải tìm kiếm thông qua các bảng phương pháp mỗi lần duy nhất các chỉ dẫn được gặp phải, bởi vì nó không thể giả định offset là giống như thời gian trước đó .

+0

Có, tôi đọc và hiểu tại sao nó có thể chậm hơn, nhưng vẫn muốn thấy bằng chứng trong thực tế. –

+0

Tôi nghi ngờ rằng bất cứ ai sẽ hy sinh đủ thời gian để tạo ra một tiêu chuẩn nghiêm túc để chứng minh những điều hiển nhiên :) –

+5

* Bên trong Máy ảo Java * đã được viết cách đây hơn 10 năm. Nó hầu như không được cập nhật với các tối ưu hóa của các JVM hiện tại. –

3

Điểm mấu chốt là bạn sẽ phải tự mình đo lường cho ứng dụng của mình . Bạn có thể nhận được các kết quả khá trực quan với JVM hiện tại. Thử thứ này đi.

file TraitAbstractPackage.scala

package traitvsabstract 

trait T1 { def x: Int; def inc: Unit } 
trait T2 extends T1 { def x_=(x0: Int): Unit } 
trait T3 extends T2 { def inc { x = x + 1 } } 

abstract class C1 { def x: Int; def inc: Unit } 
abstract class C2 extends C1 { def x_=(x0: Int): Unit } 
abstract class C3 extends C2 { def inc { x = x + 1 } } 

file TraitVsAbstract.scala

object TraitVsAbstract { 
    import traitvsabstract._ 

    class Ta extends T3 { var x: Int = 0} 
    class Tb extends T3 { 
    private[this] var y: Long = 0 
    def x = y.toInt 
    def x_=(x0: Int) { y = x0 } 
    } 
    class Tc extends T3 { 
    private[this] var xHidden: Int = 0 
    def x = xHidden 
    def x_=(x0: Int) { if (x0 > xHidden) xHidden = x0 } 
    } 

    class Ca extends C3 { var x: Int = 0 } 
    class Cb extends C3 { 
    private[this] var y: Long = 0 
    def x = y.toInt 
    def x_=(x0: Int) { y = x0 } 
    } 
    class Cc extends C3 { 
    private[this] var xHidden: Int = 0 
    def x = xHidden 
    def x_=(x0: Int) { if (x0 > xHidden) xHidden = x0 } 
    } 

    def Tbillion3(t: T3) = { 
    var i=0; while (i<1000000000) { t.inc; i+=1 }; t.x 
    } 

    def Tbillion1(t: T1) = { 
    var i=0; while (i<1000000000) { t.inc; i+=1 }; t.x 
    } 

    def Cbillion3(c: C3) = { 
    var i=0; while (i<1000000000) { c.inc; i+=1 }; c.x 
    } 

    def Cbillion1(c: C1) = { 
    var i=0; while (i<1000000000) { c.inc; i+=1 }; c.x 
    } 

    def ptime(f: => Int) { 
    val t0 = System.nanoTime 
    val ans = f.toString 
    val t1 = System.nanoTime 
    printf("Answer: %s; elapsed: %.2f seconds\n",ans,(t1-t0)*1e-9) 
    } 

    def main(args: Array[String]) { 
    for (i <- 1 to 3) { 
     println("Iteration "+i) 
     val t1s,t3s = List(new Ta, new Tb, new Tc) 
     val c1s,c3s = List(new Ca, new Cb, new Cc) 
     t1s.foreach(x => ptime(Tbillion1(x))) 
     t3s.foreach(x => ptime(Tbillion3(x))) 
     c1s.foreach(x => ptime(Cbillion1(x))) 
     c3s.foreach(x => ptime(Cbillion3(x))) 
     println 
    } 
    } 
} 

Mỗi ta nên in ra 1000000000 là câu trả lời, và thời gian thực hiện nên không (nếu JVM thực sự thông minh) hoặc miễn là phải mất thêm một tỷ con số. Nhưng ít nhất là trên hệ thống của tôi, Sun JVM tối ưu hóa ngược - chạy lặp lại nhận được chậm hơn - và các lớp trừu tượng chậm hơn so với các đặc điểm. (Bạn có thể muốn chạy với java -XX:+PrintCompilation để cố gắng tìm ra những gì xảy ra sai; Tôi nghi ngờ zombie.)

Ngoài ra, đáng chú ý là scalac -optimise không làm gì để cải thiện vấn đề - tất cả đều liên quan đến JVM.

Ngược lại, JVM JRockit chuyển thành một hiệu suất phù hợp nhất định, nhưng lại một lần nữa, đặc điểm là các lớp bị đánh bại. Vì thời gian nhất quán, tôi sẽ báo cáo: 3.35 cho các lớp (3.62s cho câu hỏi if) so với 2.51 giây cho tất cả các đặc điểm, if-statement hoặc no.

(Tôi thấy xu hướng này nói chung là đúng: Hotspot tạo ra hiệu suất cực nhanh trong một số trường hợp, và ở những trường hợp khác (như trường hợp này) bị lẫn lộn và chậm quá nhanh, JRockit không bao giờ siêu nhanh - đừng bận tâm cố gắng để có được hiệu suất giống như C ngay cả trên nguyên thủy - nhưng nó hiếm khi sai lầm.)

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