Tại sao System.arraycopy có nguồn gốc trong Java?


84

Tôi rất ngạc nhiên khi thấy trong nguồn Java có System.arraycopy là một phương thức gốc.

Tất nhiên lý do là vì nó nhanh hơn. Nhưng những thủ thuật bản địa nào mà mã có thể sử dụng để làm cho nó nhanh hơn?

Tại sao không chỉ lặp lại mảng ban đầu và sao chép từng con trỏ sang mảng mới - chắc chắn điều này không quá chậm và cồng kềnh?

Câu trả lời:


82

Trong mã gốc, nó có thể được thực hiện với một memcpy/ memmove, trái ngược với n thao tác sao chép riêng biệt. Sự khác biệt về hiệu suất là đáng kể.


@Peter, vậy trong mã gốc, bạn có thể tìm nạp với mô hình bộ nhớ Java? (Tôi chưa bao giờ có bất kỳ lý do gì để làm bất kỳ trò lừa đảo bản địa nào)
James B

8
Trên thực tế, chỉ một số trường con của arraycopycó thể được triển khai bằng cách sử dụng memcpy/ memmove. Những người khác yêu cầu kiểm tra kiểu thời gian chạy cho mỗi phần tử được sao chép.
Stephen C

1
@Stephen C, thú vị - tại sao vậy?
Péter Török

3
@ Péter Török - xem xét việc sao chép từ một tập Object[]hợp Stringcác đối tượng sang a String[]. Xem đoạn cuối của java.sun.com/javase/6/docs/api/java/lang/…
Stephen C

3
Peter, Object [] và byte [] + char [] là những cái thường được sao chép nhiều nhất, không cái nào yêu cầu kiểm tra kiểu rõ ràng. Trình biên dịch đủ thông minh KHÔNG kiểm tra trừ khi cần thiết và hầu như trong 99,9% trường hợp là không. Một phần buồn cười là các bản sao có kích thước nhỏ (ít hơn một dòng bộ nhớ cache) khá chiếm ưu thế, vì vậy "memcpy" cho những thứ có kích thước nhỏ được nhanh chóng thực sự quan trọng.
bestsss

16

Nó không thể được viết bằng Java. Mã gốc có thể bỏ qua hoặc làm sáng tỏ sự khác biệt giữa mảng Đối tượng và mảng nguyên thủy. Java không thể làm điều đó, ít nhất là không hiệu quả.

Và nó không thể được viết với một đơn lẻ memcpy(), vì ngữ nghĩa bắt buộc bởi các mảng chồng chéo.


5
Tốt, vậy memmovethì. Mặc dù tôi không nghĩ rằng nó tạo ra nhiều khác biệt trong ngữ cảnh của câu hỏi này.
Péter Török

Không phải memmove (), hãy xem nhận xét của @Stephen C về một câu trả lời khác.
Marquis of Lorne,

Đã thấy điều đó rồi, vì đó là câu trả lời của riêng tôi ;-) Nhưng dù sao cũng cảm ơn.
Péter Török

1
@Geek Mảng chồng chéo. Nếu mảng nguồn và mảng đích và các mảng giống nhau và chỉ khác nhau thì hành vi được chỉ định cẩn thận và memcpy () không tuân thủ.
Marquis of Lorne

1
không thể được viết bằng Java? Người ta không thể viết một phương thức chung để xử lý các lớp con của Đối tượng, và sau đó một phương thức cho mỗi kiểu nguyên thủy?
Michael Dorst

10

Tất nhiên, nó phụ thuộc vào việc triển khai.

HotSpot sẽ coi nó như một "nội tại" và chèn mã vào trang web cuộc gọi. Đó là mã máy, không phải mã C cũ chậm chạp. Điều này cũng có nghĩa là các vấn đề với chữ ký của phương pháp phần lớn sẽ biến mất.

Một vòng lặp sao chép đơn giản đủ đơn giản để có thể áp dụng tối ưu rõ ràng cho nó. Ví dụ: hủy cuộn vòng lặp. Chính xác những gì xảy ra lại phụ thuộc vào việc triển khai.


2
đây là một câu trả lời rất tốt :), đặc biệt. đề cập đến bản chất. w / o họ lặp đơn giản có thể là nhanh hơn vì nó thường được trải ra anyways bởi JIT
bestsss

4

Trong các thử nghiệm của riêng tôi, System.arraycopy () để sao chép mảng nhiều chiều nhanh hơn từ 10 đến 20 lần so với việc xen kẽ cho các vòng lặp:

float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9]
float[][] fooCpy = new float[foo.length][foo[0].length];
long lTime = System.currentTimeMillis();
System.arraycopy(foo, 0, fooCpy, 0, foo.length);
System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms");
lTime = System.currentTimeMillis();

for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        fooCpy[i][j] = foo[i][j];
    }
}
System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms");
for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        if (fooCpy[i][j] != foo[i][j])
        {
            System.err.println("ERROR at " + i + ", " + j);
        }
    }
}

Bản in này:

System.arraycopy() duration: 1 ms
loop duration: 16 ms

9
Mặc dù câu hỏi này đã cũ, nhưng chỉ để ghi lại: Đây KHÔNG phải là một điểm chuẩn công bằng (hãy để một mình câu hỏi nếu điểm chuẩn như vậy có ý nghĩa ngay từ đầu). System.arraycopythực hiện một bản sao cạn (chỉ các tham chiếu đến các bên trong float[]được sao chép), trong khi các for-loops lồng nhau của bạn thực hiện một bản sao sâu ( floatbằng cách float). Thay đổi đối với fooCpy[i][j]sẽ được phản ánh khi foosử dụng System.arraycopy, nhưng sẽ không sử dụng for-loops lồng nhau .
misberner

4

Có một vài lý do:

  1. JIT không có khả năng tạo mã mức thấp hiệu quả như mã C được viết thủ công. Sử dụng mức C thấp có thể cho phép rất nhiều tối ưu hóa gần như không thể thực hiện được đối với trình biên dịch JIT chung.

    Xem liên kết này để biết một số thủ thuật và so sánh tốc độ của các triển khai C viết tay (memcpy, nhưng nguyên tắc giống nhau): Kiểm tra này Tối ưu hóa Memcpy cải thiện tốc độ

  2. Phiên bản C khá độc lập với kiểu và kích thước của các thành viên mảng. Không thể làm điều tương tự trong java vì không có cách nào để lấy nội dung mảng dưới dạng một khối bộ nhớ thô (ví dụ: con trỏ).


1
Mã Java có thể được tối ưu hóa. Trong thực tế những gì thực sự xảy ra là mã máy được tạo ra mà là hiệu quả hơn so với C.
Tom Hawtin - tackline

Tôi đồng ý rằng đôi khi mã JITed sẽ được tối ưu hóa cục bộ tốt hơn vì nó biết nó đang chạy bộ xử lý nào. Tuy nhiên, vì nó là "đúng lúc" nên nó sẽ không bao giờ có thể sử dụng tất cả những tối ưu không cục bộ mà mất nhiều thời gian hơn để thực thi. Ngoài ra, nó sẽ không bao giờ có thể khớp với mã C được làm thủ công (cũng có thể tính đến bộ xử lý và phủ nhận một phần các ưu điểm của JIT, bằng cách tổng hợp cho một bộ xử lý cụ thể hoặc bằng một số loại kiểm tra thời gian chạy).
Hrvoje Prgeša

1
Tôi nghĩ rằng nhóm biên dịch Sun JIT sẽ tranh chấp nhiều điểm trong số đó. Ví dụ: tôi tin rằng HotSpot thực hiện tối ưu hóa toàn cục để loại bỏ việc điều phối phương thức không cần thiết và không có lý do gì khiến JIT không thể tạo mã cụ thể của bộ xử lý. Sau đó, có một điểm mà một trình biên dịch JIT có thể thực hiện tối ưu hóa nhánh dựa trên hành vi thực thi của ứng dụng hiện tại đang chạy.
Stephen C

@Stephen C - điểm xuất sắc về tối ưu hóa nhánh, ngoài ra, bạn cũng có thể thực hiện lập hồ sơ hiệu suất tĩnh với trình biên dịch C / C ++ để đạt được hiệu quả tương tự. Tôi cũng nghĩ rằng điểm phát sóng có 2 chế độ hoạt động - các ứng dụng máy tính để bàn sẽ không sử dụng tất cả các tối ưu hóa có sẵn để đạt được thời gian khởi động hợp lý, trong khi các ứng dụng máy chủ sẽ được tối ưu hóa mạnh mẽ hơn. Nhìn chung, bạn nhận được một số lợi thế, nhưng bạn cũng mất một số lợi thế.
Hrvoje Prgeša

1
System.arraycopy không được thực hiện sử dụng C, trong đó loại làm mất hiệu lực câu trả lời này
Nitsan Wakart
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.