Câu hỏi Sử dụng System.arraycopy (…) có tốt hơn là vòng lặp for để sao chép mảng không?


Tôi muốn tạo một mảng các đối tượng kết hợp hai mảng nhỏ hơn.

Chúng không thể rỗng, nhưng kích thước có thể là 0.

Tôi không thể chọn giữa hai cách này: chúng có tương đương hay là hiệu quả hơn (ví dụ: system.arraycopy () sao chép toàn bộ các khối)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

hoặc là

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

Sự khác biệt duy nhất là giao diện của mã?

CHỈNH SỬA: cảm ơn câu hỏi được liên kết, nhưng dường như họ có một cuộc thảo luận chưa được giải quyết:

Nó có thực sự nhanh hơn nếu it is not for native types: byte [], Đối tượng [], char []? trong tất cả các trường hợp khác, kiểm tra kiểu được thực thi, đó là trường hợp của tôi và do đó sẽ tương đương ... không?

Trên một câu hỏi được liên kết khác, họ nói rằng the size matters a lot, cho kích thước> 24 hệ thống.arraycopy () thắng, cho nhỏ hơn 10, hướng dẫn sử dụng cho vòng lặp là tốt hơn ...

Bây giờ tôi thực sự bối rối.


76
2017-09-05 14:15


gốc


arraycopy() là một cuộc gọi bản địa, chắc chắn là nhanh hơn. - Sotirios Delimanolis
Bạn đã thử điểm chuẩn hai triển khai khác nhau chưa? - Alex
Hãy xem ở đây: stackoverflow.com/questions/2772152/… - Sotirios Delimanolis
Bạn nên chọn bất cứ điều gì bạn thấy dễ đọc nhất và dễ bảo trì nhất trong tương lai. Chỉ khi bạn đã xác định rằng đây là nguồn gốc của một nút cổ chai, bạn nên thay đổi cách tiếp cận của bạn. - arshajii
Đừng sáng tạo lại bánh xe! - camickr


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


public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

Tôi biết các bài kiểm tra JUnit không thực sự tốt nhất cho điểm chuẩn, nhưng
testHardCopyBytes mất 0.157 giây để hoàn tất

testArrayCopyBytes mất 0,086 giây để hoàn thành.

Tôi nghĩ rằng nó phụ thuộc vào máy ảo, nhưng có vẻ như nó sao chép các khối bộ nhớ thay vì sao chép các phần tử mảng đơn. Điều này hoàn toàn sẽ làm tăng hiệu suất.

CHỈNH SỬA:
Có vẻ như hiệu suất của System.arraycopy là tất cả các nơi. Khi chuỗi được sử dụng thay vì byte và mảng nhỏ (kích thước 10), Tôi nhận được những kết quả này:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

Đây là những gì nó trông giống như khi mảng có kích thước 0x1000000. Có vẻ như System.arraycopy chắc chắn thắng với các mảng lớn hơn.

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

Thật kỳ lạ!

Cảm ơn, Daren, đã chỉ ra rằng các tham chiếu sao chép khác nhau. Nó làm cho vấn đề này trở nên thú vị hơn nhiều!


70
2017-09-05 14:28



Cảm ơn vì nỗ lực của bạn, nhưng bạn đã bỏ lỡ các điểm có vẻ rất quan trọng: các loại không phải là bản địa (tạo một lớp ngẫu nhiên với anythign để mảng chứa tham chiếu) và kích thước ... dường như cho kích thước mảng nhỏ hơn, hướng dẫn sử dụng cho vòng lặp nhanh hơn. Chăm sóc để sửa lỗi này? - Daren
Oh wow, bạn nói đúng! Đó là sự quan tâm. Đặt các chuỗi trong các mảng thay vì byte tạo ra sự khác biệt lớn: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>> - Trent Small
Bạn nhận được kết quả gì? cũng thú vị sẽ được thử với kích thước mảng = 10 ... cảm ơn! (Tôi muốn tôi đã có IDE của tôi ở đây, tôi đang mã hóa mà không có trình biên dịch). - Daren
Bây giờ tôi đã bọc chúng trong một số cuộc gọi System.nanoTime () và đặt kích thước = 10 để xem có bao nhiêu nano giây. Hình như cho các mảng nguyên thủy nhỏ, vòng lặp là tốt hơn; cho tài liệu tham khảo, arrayCopy là tốt hơn .: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>> - Trent Small
Kết quả rất thú vị! cảm ơn bạn rất nhiều! bạn có thể vui lòng chỉnh sửa câu trả lời của bạn để bao gồm điều này, và tôi sẽ sẵn sàng chấp nhận nó để nó vẫn là người đầu tiên cho tất cả để xem ... bạn đã nhận được phiếu bầu của tôi lên. :) - Daren


Arrays.copyOf(T[], int) dễ đọc hơn. Internaly nó sử dụng System.arraycopy() đó là một cuộc gọi bản địa.

Bạn không thể làm cho nó nhanh hơn!


30
2017-09-05 14:19



có vẻ như bạn có thể tùy thuộc vào những điều khá mới mẻ, nhưng cảm ơn vì đã chỉ ra rằng fucntion mà tôi không biết và thực sự dễ đọc hơn. :) - Daren
Vâng! nó phụ thuộc vào một vài điều như @Svetoslav Tsolov nói. tôi chỉ muốn chỉ ra Arrays.copyOf - Philipp Sander
copyOf không thể luôn thay thế arraycopy, nhưng nó phù hợp cho trường hợp sử dụng này. - Blaisorblade
NB Nếu bạn đang nhìn vào hiệu suất thì điều này sẽ không nhanh như System.arraycopy () vì nó yêu cầu cấp phát bộ nhớ. Nếu đây là trong một vòng lặp sau đó phân bổ lặp đi lặp lại sẽ dẫn đến thu gom rác thải mà sẽ là một hit hiệu suất lớn. - Will Calderwood
@PhilippSander Chỉ cần kiểm tra tôi đã không được ngu ngốc tôi thêm mã để sao chép của một mảng 1MB trong vòng lặp trò chơi của tôi, mà hầu như không bao giờ cháy GC. Với Array.copyOf (), DVM của tôi đã gọi GC 5 lần mỗi giây và trò chơi trở nên cực kỳ chậm chạp. Tôi nghĩ rằng nó là an toàn để nói có phân bổ bộ nhớ xảy ra. - Will Calderwood


Nó phụ thuộc vào máy ảo, nhưng System.arraycopy sẽ cung cấp cho bạn gần nhất bạn có thể nhận được để thực hiện bản địa.

Tôi đã làm việc trong 2 năm với tư cách là nhà phát triển java cho các hệ thống nhúng (nơi hiệu suất là ưu tiên lớn) và ở khắp mọi nơi System.arraycopy có thể được sử dụng, tôi đã sử dụng nó / nhìn thấy nó trong mã hiện có. Nó luôn được ưu tiên hơn các vòng lặp khi hiệu suất là một vấn đề. Nếu hiệu suất không phải là một vấn đề lớn, tôi sẽ đi với vòng lặp, mặc dù. Dễ đọc hơn nhiều.


13
2017-09-05 14:18



Những thứ như kích thước mảng và kiểu (cơ bản vs kế thừa) dường như ảnh hưởng đến preformance. - Daren
Yeah, nó không phải là 'bản địa hiệu suất' cho mỗi se, đó là lý do tại sao tôi nói tôi 'chủ yếu' sử dụng nó nơi tôi có thể (bạn sẽ nhận thấy nó chủ yếu là thắng trên sao chép vòng lặp). Tôi đoán lý do là: khi đó là một mảng nhỏ của một kiểu nguyên thủy, 'chi phí để gọi' lớn hơn hiệu suất tăng. Sử dụng JNI có thể làm suy giảm hiệu suất cho cùng một lý do - bản thân mã nguồn gốc là nhanh, nhưng gọi nó là từ một quá trình java - không quá nhiều. - s.ts
Tôi thấy, cảm ơn bạn rất nhiều vì đầu vào của bạn! - Daren
Chỉnh sửa nhỏ, Arrays.copy không phải là JNI, nó là một nội tại. Intrinsics nhanh hơn nhiều so với JNI. Làm thế nào và khi nào trình biên dịch JIT biến nó thành một nội tại phụ thuộc vào JVM / trình biên dịch được sử dụng. - Nitsan Wakart
Arrays.copy không tồn tại, Arrays.copyOf là một hàm thư viện. - Blaisorblade


Thực thi các phương thức gốc như Arrays.copyOf(T[], int) không có một số chi phí nhưng nó không có nghĩa là nó không phải là nhanh như bạn đang thực hiện nó bằng cách sử dụng JNI.

Cách dễ nhất là viết điểm chuẩn và kiểm tra.

Bạn có thể kiểm tra Arrays.copyOf(T[], int) nhanh hơn bình thường của bạn for vòng lặp.

Mã điểm chuẩn từ đây: -

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy() sử dụng JNI (Java Native Interface) để sao chép một mảng (hoặc một phần của nó), do đó, nó là blazingly nhanh, như bạn có thể xác nhận đây


6
2017-09-05 14:27



Mã này sử dụng int [] bạn có thể thử nó với String [] (được khởi tạo với các giá trị khác nhau: "1", "2", v.v ... vì chúng không thay đổi được - Daren


Thay vì dựa vào suy đoán và thông tin có thể đã lỗi thời, tôi đã chạy một số điểm chuẩn bằng cách sử dụng . Trong thực tế, Caliper đi kèm với một số ví dụ, bao gồm CopyArrayBenchmark đo chính xác câu hỏi này! Tất cả những gì bạn phải làm là chạy

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

Kết quả của tôi dựa trên Máy chủ Java 64-bit của Oracle HotSpot (TM), 1.8.0_31-b13, chạy trên MacBook Pro giữa năm 2010 (macOS 10.11.6 với Intel Arrandale i7, 8 GiB RAM). Tôi không tin rằng việc đăng dữ liệu thời gian thô thật hữu ích. Thay vào đó, tôi sẽ tóm tắt các kết luận với các hình ảnh hỗ trợ.

Tóm tắt:

  • Viết hướng dẫn for vòng lặp để sao chép từng phần tử vào một mảng mới được tạo ra là không bao giờ thuận lợi, cho dù là các mảng ngắn hay mảng dài.
  • Arrays.copyOf(mảng, mảng.length) và mảng.clone() cả hai đều nhanh chóng. Hai kỹ thuật này gần như giống nhau về hiệu năng; cái nào bạn chọn là một vấn đề của hương vị.
  • System.arraycopy(src, 0, dest, 0, src.length) gần như nhanh như Arrays.copyOf(mảng, mảng.length) và mảng.clone(), nhưng không hoàn toàn nhất quán như vậy. (Xem trường hợp cho 50000 ints.) Bởi vì điều đó, và độ dài của cuộc gọi, tôi sẽ khuyên bạn nên System.arraycopy() nếu bạn cần kiểm soát tốt các yếu tố được sao chép ở đâu.

Dưới đây là các ô thời gian:

Timings for copying arrays of length 5 Timings for copying arrays of length 500 Timings for copying arrays of length 50000


4
2018-03-14 04:36





Làm sao có thể Arrays.copyOf nhanh hơn rồi System.arraycopy nếu đây là việc thực hiện copyOf:

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

4
2018-05-10 11:37





System.arraycopy() là một cuộc gọi bản địa hoạt động sao chép trực tiếp tại bộ nhớ. Bản sao bộ nhớ đơn sẽ luôn luôn nhanh hơn vòng lặp for của bạn


3
2017-09-05 14:21



Tôi đã đọc cho các loại không phải bản địa (bất kỳ lớp nào được tạo ra, như của tôi) nó có thể không hiệu quả ... và cho các kích thước kích cỡ nhỏ (trường hợp của tôi) hướng dẫn sử dụng cho vòng lặp có thể tốt hơn ... chăm sóc bình luận? - Daren
Thật vậy, System.arraycopy () có một số chi phí cho các mảng nhỏ (n = ~ 10) một vòng lặp thực sự nhanh hơn - RecursiveExceptionException