Câu hỏi Tại sao System.arraycopy có nguồn gốc trong Java?


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

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

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


76
2018-05-05 10:01


gốc




Các câu trả lời:


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


73
2018-05-05 10:04



@ Peter, vì vậy trong mã gốc bạn có thể thích ứng với mô hình bộ nhớ Java? (Tôi chưa bao giờ có bất kỳ nguyên nhân nào để làm bất kỳ malarkey nào) - James B
Trên thực tế, chỉ một số phụ của arraycopy có thể được triển khai bằ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
@Stephen C, thú vị - tại sao vậy? - Péter Török
@ Péter Török - xem xét sao chép từ một Object[] dân cư với String đối tượng cho một String[]. Xem đoạn cuối của java.sun.com/javase/6/docs/api/java/lang/… - Stephen C
Peter, Object [] và byte [] + char [] là những cái được sao chép thường xuyên nhất, không có cái nào trong số chúng 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 nó không phải. 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 phối, do đó, "memcpy" cho các công cụ có kích thước nhỏ đang nhanh chóng thực sự quan trọng. - bestsss


Nó không thể được viết bằng Java. Mã nguồn gốc có thể bỏ qua hoặc bỏ qua sự khác biệt giữa các mảng của đố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 đĩa đơn memcpy(), vì ngữ nghĩa được yêu cầu bởi các mảng chồng chéo.


15
2018-05-05 10:09



Tốt thôi, memmove sau đó. Mặc dù tôi không nghĩ rằng nó tạo ra sự khác biệt nhiều trong ngữ cảnh của câu hỏi này. - Péter Török
Không phải memmove (), hoặc xem nhận xét của @Stephen C về câu trả lời khác. - user207421
Thấy rằng đã, kể từ đó đã xảy ra được câu trả lời của riêng tôi ;-) Nhưng nhờ anyway. - Péter Török
@Geek Mảng chồng chéo lên nhau. Nếu các mảng nguồn và đích và giống nhau và chỉ có hiệu số là khác nhau, hành vi được chỉ định một cách cẩn thận, và memcpy () không tuân thủ. - user207421
Nó không thể được viết bằng Java? Không thể viết một phương pháp chung để xử lý các lớp con của đối tượng, và sau đó một cho mỗi kiểu nguyên thủy? - anthropomorphic


Đó là, tất nhiên, thực hiện phụ thuộc.

HotSpot sẽ xử lý nó như là một "nội tại" và chèn mã tại trang web cuộc gọi. Đó là mã máy, không làm chậm mã C cũ. Đ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 biến mất.

Một vòng lặp bản sao đơn giản là đủ đơn giản để tối ưu hóa rõ ràng có thể được áp dụng cho nó. Ví dụ vòng lặp unrolling. Chính xác những gì xảy ra là một lần nữa thực hiện phụ thuộc.


9
2018-05-05 10:17



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


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

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

4
2017-11-28 14:44



Mặc dù câu hỏi này là cũ, chỉ cho các hồ sơ: Đâ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 như một điểm chuẩn sẽ có ý nghĩa ở nơi đầu tiên). System.arraycopy một bản sao nông (chỉ có tài liệu tham khảo bên trong float[]s được sao chép), trong khi lồng nhau của bạn for-loops thực hiện một bản sao sâu (float bởi float). Thay đổi đối với fooCpy[i][j] sẽ được phản ánh trong foo sử dụng System.arraycopy, nhưng sẽ không sử dụng lồng nhau forvòng lặp. - misberner


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

  1. JIT dường như không tạo ra mã cấp thấp hiệu quả như mã C được viết thủ công. Sử dụng mức thấp C có thể cho phép rất nhiều tối ưu hóa gần như không thể làm cho một 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 việc thực hiện viết tay C (memcpy, nhưng nguyên tắc là như nhau): Tối ưu Memcpy cải thiện tốc độ

  2. Phiên bản C khá độc lập với loại 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 như một khối dữ liệu thô (ví dụ: con trỏ).


3
2018-05-05 10:15



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 đó 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 localy tốt hơn vì nó biết bộ xử lý nào được chạy trên đó. Tuy nhiên, vì nó là "chỉ trong thời gian", nó sẽ không bao giờ có thể sử dụng tất cả những sự tối ưu hóa phi địa phương 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 tạo thủ công (cũng có thể lấy bộ xử lý trong tài khoản và loại bỏ một phần lợi thế của JIT, hoặc bằng cách biên dịch cho một bộ xử lý cụ thể hoặc bằng một số kiểm tra thời gian chạy). - Hrvoje Prgeša
Tôi nghĩ rằng nhóm biên dịch Sun JIT sẽ tranh luận nhiều trong số những điểm đó. Ví dụ, tôi tin rằng HotSpot thực hiện tối ưu hóa toàn cầu để loại bỏ việc gửi đi phương thức không cần thiết, và không có lý do tại sao một JIT không thể tạo ra mã bộ xử lý cụ thể. Sau đó, có một điểm mà trình biên dịch JIT có thể thực hiện tối ưu hóa chi nhánh dựa trên hành vi thực hiện 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, bạn cũng có thể thực hiện lược tả hiệu năng tĩnh với trình biên dịch C / C ++ để đạt được hiệu ứng 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 hơn. Tất cả trong tất cả, bạn nhận được một số lợi thế, nhưng bạn cũng mất một số. - Hrvoje Prgeša
System.arrayCopy không được thực hiện bằng cách sử dụng C, loại không hợp lệ hóa câu trả lời này - Nitsan Wakart