Các ngôn ngữ OO hiện đại có thể cạnh tranh với hiệu suất lưu trữ mảng của C ++ không?


40

Tôi chỉ nhận thấy rằng mọi ngôn ngữ lập trình OO hiện đại mà tôi ít nhất là quen thuộc (về cơ bản chỉ là Java, C # và D) cho phép các mảng covariant. Đó là, một mảng chuỗi là một mảng đối tượng:

Object[] arr = new String[2];   // Java, C# and D allow this

Mảng covariant là một lỗ hổng trong hệ thống kiểu tĩnh. Chúng tạo ra các lỗi loại có thể không thể được phát hiện tại thời gian biên dịch, do đó, mỗi lần ghi vào một mảng phải được kiểm tra trong thời gian chạy:

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

Đây có vẻ là một hiệu suất khủng khiếp nếu tôi làm nhiều cửa hàng mảng.

C ++ không có mảng covariant, do đó không cần phải thực hiện kiểm tra thời gian chạy như vậy, điều đó có nghĩa là không có hình phạt hiệu năng.

Có bất kỳ phân tích được thực hiện để giảm số lượng kiểm tra thời gian chạy cần thiết? Ví dụ: nếu tôi nói:

arr[1] = arr[0];

người ta có thể lập luận rằng các cửa hàng có thể không thể thất bại. Tôi chắc chắn có rất nhiều tối ưu hóa có thể khác mà tôi chưa từng nghĩ đến.

Các trình biên dịch hiện đại có thực sự thực hiện các loại tối ưu hóa này không, hay tôi phải sống với thực tế là, ví dụ, Quicksort luôn kiểm tra thời gian chạy không cần thiết O (n log n)?

Các ngôn ngữ OO hiện đại có thể tránh được chi phí được tạo ra bằng cách hỗ trợ các mảng đồng biến thể không?


7
Tôi bối rối tại sao bạn đề xuất C ++ nhanh hơn các ngôn ngữ khác tại một tính năng mà C ++ thậm chí không hỗ trợ.

17
@eco: C ++ nhanh hơn với truy cập mảng nó không hỗ trợ mảng đồng biến thể. Fred muốn biết liệu "các ngôn ngữ OO hiện đại" có thể vượt qua được các mảng đồng biến thể để tiến gần hơn đến tốc độ C ++ hay không.
Vịt Mooing

13
"mã biên dịch nhưng ném ngoại lệ vào thời gian chạy là tin xấu"

4
@Jlie Và hàng triệu người viết mã đáng tin cậy, có khả năng mở rộng cao bằng các ngôn ngữ động. Imo: Nếu bạn không viết các trường hợp kiểm tra cho mã của mình, tôi không quan tâm liệu có lỗi trình biên dịch hay không, dù sao tôi cũng sẽ không tin nó.
Voo

7
@Jlie Và khi nào bạn sẽ mong đợi ngoại lệ nhưng trong thời gian chạy? Vấn đề không phải là mã đưa ra các ngoại lệ trong thời gian chạy - có rất nhiều trường hợp có ý nghĩa tốt - vấn đề là mã được đảm bảo là sai mà không bị trình biên dịch bắt giữ tĩnh mà thay vào đó dẫn đến ngoại lệ thời gian chạy.
Jonathan M Davis

Câu trả lời:


33

D không có mảng covariant. Nó cho phép họ trước khi phát hành gần đây nhất ( dmd 2.057 ), nhưng lỗi đó đã được sửa.

Một mảng trong D thực sự chỉ là một cấu trúc với một con trỏ và chiều dài:

struct A(T)
{
    T* ptr;
    size_t length;
}

Kiểm tra giới hạn được thực hiện bình thường khi lập chỉ mục một mảng, nhưng nó bị xóa khi bạn biên dịch -release. Vì vậy, trong chế độ phát hành, không có sự khác biệt hiệu năng thực sự giữa các mảng trong C / C ++ và các mảng trong D.


Xuất hiện không - d-programming-lingu.org/arrays.html nói "Một mảng tĩnh T[dim]có thể được chuyển đổi hoàn toàn thành một trong những điều sau đây: ... U[]... Một mảng động T[]có thể được chuyển đổi hoàn toàn thành một trong những điều sau đây : U[]. .. đâu Ulà lớp cơ sở của T. "
Ben Voigt

2
@BenVoigt Sau đó, các tài liệu trực tuyến cần được cập nhật. Họ không phải lúc nào cũng cập nhật 100%. Chuyển đổi sẽ hoạt động với const U[], kể từ đó, bạn không thể gán loại sai cho các thành phần của mảng, nhưng T[]chắc chắn không chuyển đổi U[]miễn U[]là có thể thay đổi. Cho phép các mảng covariant như trước đây là một lỗ hổng thiết kế nghiêm trọng hiện đã được sửa chữa.
Jonathan M Davis

@JonathanMDavis: Mảng covariant là icky, nhưng hoạt động tốt cho kịch bản cụ thể của mã sẽ chỉ ghi vào một phần tử mảng đã được đọc từ cùng mảng đó . Làm thế nào D cho phép một người viết một phương thức có thể sắp xếp các mảng kiểu tùy ý?
supercat

@supercat Nếu bạn muốn viết một hàm sắp xếp các kiểu tùy ý, thì hãy tạo khuôn mẫu cho nó. ví dụ: dlang.org/phobos/std_alerskym.html#sort
Jonathan M Davis

21

Vâng, một tối ưu hóa quan trọng là đây:

sealed class Foo
{
}

Trong C #, lớp này không thể là siêu kiểu cho bất kỳ loại nào, vì vậy bạn có thể tránh việc kiểm tra một mảng loại Foo.

Và với câu hỏi thứ hai, trong các mảng đồng biến thể F # không được phép (nhưng tôi đoán kiểm tra sẽ vẫn còn trong CLR trừ khi nó không cần thiết trong tối ưu hóa khi chạy)

let a = [| "st" |]
let b : System.Object[] = a // Fails

https://stackoverflow.com/questions/7339013/array-covariance-in-f

Một vấn đề hơi liên quan là kiểm tra ràng buộc mảng. Đây có thể là một bài đọc thú vị (nhưng cũ) về tối ưu hóa được thực hiện trong CLR (hiệp phương sai cũng được đề cập 1 vị trí): http://bloss.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bound -check-Elimination-in-the-clr.aspx


2
Scala cũng ngăn chặn cấu trúc này: val a = Array("st"); val b: Array[Any] = alà bất hợp pháp. (Tuy nhiên, các mảng trong Scala là ... ma thuật đặc biệt ... do JVM cơ bản được sử dụng.)
pst

13

Câu trả lời Java:

Tôi lấy nó mà bạn chưa thực sự chuẩn mã, phải không? Nói chung, 90% tất cả các phôi động trong Java là miễn phí vì JIT có thể loại bỏ chúng (quicksort phải là một ví dụ tốt cho điều này) và phần còn lại là một ld/cmp/brchuỗi hoàn toàn có thể dự đoán được (nếu không, tại sao địa ngục lại ném mã của bạn tất cả những ngoại lệ diễn viên năng động?).

Chúng tôi thực hiện tải sớm hơn nhiều so với so sánh thực tế, chi nhánh được dự đoán chính xác trong 99.9999% (đã được thống kê!) Trong tất cả các trường hợp vì vậy chúng tôi không trì hoãn đường ống (giả sử chúng tôi không nhấn bộ nhớ với tải, nếu không tốt sẽ được chú ý, nhưng sau đó tải là cần thiết dù sao). Do đó, chi phí là 1 chu kỳ đồng hồ NẾU JIT không thể tránh được việc kiểm tra.

Một số chi phí? Chắc chắn, nhưng tôi nghi ngờ bạn sẽ nhận thấy nó ..


Để giúp hỗ trợ câu trả lời của tôi, vui lòng xem blog này của Tiến sĩ Cliff Click thảo luận về hiệu suất Java so với C.


10

D không cho phép mảng covariant.

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

Như bạn nói, nó sẽ là một lỗ hổng trong hệ thống loại để cho phép điều này.

Bạn có thể được tha thứ cho lỗi lầm, vì lỗi này chỉ mới được sửa trong DMD mới nhất, được phát hành vào ngày 13 tháng 12.

Truy cập mảng trong D cũng nhanh như trong C hoặc C ++.


Theo d-programming-lingu.org/arrays.html "Một mảng tĩnh T [dim] có thể được chuyển đổi hoàn toàn thành một trong những điều sau đây: ... U [] ... Một mảng động T [] có thể được chuyển đổi hoàn toàn theo một trong những điều sau đây: U [] ... trong đó U là lớp cơ sở của T. "
Ben Voigt

1
@BenVoigt: Tài liệu hết hạn.
BCS

1
@BenVoigt: Tôi đã tạo một yêu cầu kéo để cập nhật tài liệu. Hy vọng điều này sẽ được giải quyết sớm. Cảm ơn đã chỉ ra điều đó.
Peter Alexander

5

Từ thử nghiệm tôi đã thực hiện trên một máy tính xách tay giá rẻ, sự khác biệt giữa việc sử dụng int[]Integer[]khoảng 1,0 ns. Sự khác biệt có thể là do kiểm tra thêm cho loại.

Nói chung, các đối tượng chỉ được sử dụng cho logic mức cao hơn khi không phải mọi ns đều được tính. Nếu bạn cần lưu mọi ns, tôi sẽ tránh sử dụng các cấu trúc cấp cao hơn như Đối tượng. Bài tập một mình có khả năng là yếu tố rất nhỏ trong bất kỳ chương trình thực tế nào. ví dụ: tạo một Object mới trên cùng một máy là 5 ns.

Các cuộc gọi để so sánh có thể sẽ đắt hơn nhiều, đặc biệt nếu bạn sử dụng một đối tượng phức tạp như String.


2

Bạn hỏi về các ngôn ngữ OO hiện đại khác? Chà, Delphi tránh hoàn toàn vấn đề này bằng cách stringlà một người nguyên thủy, không phải là một đối tượng. Vì vậy, một chuỗi các chuỗi chính xác là một chuỗi các chuỗi và không có gì khác, và bất kỳ hoạt động nào trên chúng đều nhanh như mã gốc, không có loại kiểm tra chi phí.

Tuy nhiên, mảng chuỗi không được sử dụng rất thường xuyên; Lập trình viên Delphi có xu hướng ủng hộ TStringListlớp học. Đối với một lượng chi phí tối thiểu, nó cung cấp một tập hợp các phương thức nhóm chuỗi hữu ích trong nhiều tình huống mà lớp đã được so sánh với một con dao quân đội Thụy Sĩ. Vì vậy, nó là thành ngữ để sử dụng một đối tượng danh sách chuỗi thay vì một mảng chuỗi.

Đối với các đối tượng khác nói chung, vấn đề không tồn tại bởi vì trong Delphi, như trong C ++, các mảng không phải là biến số, để ngăn chặn các loại lỗ hệ thống được mô tả ở đây.


1

hoặc tôi phải sống với thực tế là, ví dụ, Quicksort luôn kiểm tra thời gian chạy không cần thiết O (n log n)?

Hiệu suất CPU không đơn điệu, điều đó có nghĩa là các chương trình dài hơn có thể nhanh hơn các chương trình ngắn hơn (điều này phụ thuộc vào CPU và điều này đúng với các kiến ​​trúc x86 và amd64 phổ biến). Vì vậy, có thể một chương trình thực hiện kiểm tra ràng buộc trên các mảng thực sự nhanh hơn chương trình được suy ra từ trước bằng cách loại bỏ các kiểm tra ràng buộc này.

Lý do của hành vi này là việc kiểm tra ràng buộc sửa đổi sự liên kết của mã trong bộ nhớ, sẽ sửa đổi tần suất của các lần truy cập bộ đệm, v.v.

Vì vậy, có, sống với thực tế là Quicksort luôn thực hiện kiểm tra giả O (n log n) và tối ưu hóa sau khi định hình.


1

Scala là một ngôn ngữ OO có bất biến, thay vì covariant, mảng. Nó nhắm mục tiêu vào JVM, do đó không có hiệu năng chiến thắng ở đó, nhưng nó tránh được sự không phù hợp chung cho cả Java và C #, làm ảnh hưởng đến an toàn loại của chúng vì lý do tương thích ngược.

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.