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


89

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

Chúng không được rỗng, nhưng kích thước có thể bằng 0.

Tôi không thể chọn giữa hai cách này: chúng tương đương hay một cách hiệu quả hơn (ví dụ: system.arraycopy () sao chép toàn bộ 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 vì câu hỏi được liên kết, nhưng dường như họ vẫn còn một cuộc thảo luận chưa được giải quyết:

Nó có thực sự nhanh hơn không nếu it is not for native types: byte [], Object [], char []? trong tất cả các trường hợp khác, kiểm tra kiểu được thực hiện, đó sẽ là trường hợp của tôi và như vậy 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, đối với kích thước> 24 system.arraycopy () thắng, đối với nhỏ hơn 10, vòng lặp hướng dẫn sử dụng sẽ tốt hơn ...

Giờ tôi thực sự bối rối.


16
arraycopy()là một cuộc gọi riêng, chắc chắn là nhanh hơn.
Sotirios Delimanolis

4
Bạn đã thử đo điểm chuẩn cho hai cách triển khai khác nhau chưa?
Alex


15
Bạn nên chọn bất kỳ cái nào 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 nút thắt, bạn mới nên thay đổi cách tiếp cận của mình.
arshajii

1
Đừng phát minh lại bánh xe!
camickr

Câu trả lời:


87
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ự là tốt nhất để đo điểm chuẩn, nhưng
testHardCopyBytes mất 0,157 giây để hoàn thành

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 lẻ. Đ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 đang ở khắp nơi. Khi Chuỗi được sử dụng thay vì byte và các mảng nhỏ (kích thước 10), tôi nhận được các kết quả sau:

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

Đây là giao diện của nó 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!


2
Cảm ơn bạn vì nỗ lực của bạn, nhưng bạn đã bỏ lỡ những điểm có vẻ quan trọng: kiểu 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 ... có vẻ như kích thước mảng nhỏ hơn, vòng lặp thủ công nhanh hơn. Bạn muốn sửa lỗi này?
Daren

2
Ồ, bạn nói đúng! Thật thú vị. Đặt Strings trong những mảng thay vì byte làm cho một sự khác biệt rất lớn: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Trent nhỏ

Bạn nhận được kết quả gì sau đó? cũng thú vị sẽ được thử với kích thước mảng = 10 ... cảm ơn! (Tôi ước tôi có IDE của mình ở đây, tôi đang viết mã mà không cần trình biên dịch).
Daren

Bây giờ tôi đã gói chúng trong một số lệnh gọi System.nanoTime () và đặt size = 10 để xem mỗi nano giây mất bao nhiêu nano giây. Có vẻ như đối với các mảng nguyên thủy nhỏ, các vòng lặp là tốt hơn; để tham khảo, arrayCopy tốt hơn: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent Nhỏ

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 mình để bao gồm điều này, và tôi sẽ sẵn lòng chấp nhận nó để nó luôn là người đầu tiên mọi người nhìn thấy ... bạn đã có phiếu bầu của tôi. :)
Daren

36

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

Bạn không thể lấy nó nhanh hơn!


Có vẻ như bạn có thể phụ thuộc vào những thứ khá mới mẻ, nhưng cảm ơn vì đã chỉ ra rằng sự thật mà tôi không biết và thực sự dễ đọc hơn. :)
Daren

Đúng! nó phụ thuộc vào một số thứ như @Svetoslav Tsolov đã nói. tôi chỉ muốn chỉ ra Arrays.copyOf
Philipp Sander

2
copyOfkhông thể luôn thay thế arraycopy, nhưng nó thích hợp cho trường hợp sử dụng này.
Blaisorblade

1
NB Nếu bạn đang xem xét 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 điều này nằm trong một vòng lặp thì việc phân bổ lặp lại sẽ dẫn đến việc thu gom rác, đây sẽ là một tác động lớn về hiệu suất.
Will Calderwood

1
@PhilippSander Chỉ để kiểm tra xem tôi không ngu ngốc, tôi đã thêm mã vào bản sao của mảng 1MB trong vòng lặp trò chơi của mình, mà hầu như không bao giờ kích hoạt 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ỳ lag. Tôi nghĩ rằng có thể nói rằng có sự phân bổ bộ nhớ đang diễn ra là an toàn.
Will Calderwood

17

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

Tôi đã làm việc 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à ở mọi nơi có thể sử dụng System.arraycopy, tôi hầu như đã sử dụng nó / thấy nó được sử dụng 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 đề. Tuy nhiê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. Dễ đọc hơn nhiều.


Những thứ như kích thước và kiểu mảng (cơ bản so với kế thừa) dường như ảnh hưởng đến hiệu suất.
Daren

2
Vâng, đó không phải là 'hiệu suất gốc', đó là lý do tại sao tôi đã nói rằng tôi 'chủ yếu' sử dụng nó ở những nơi tôi có thể (bạn sẽ nhận thấy nó chủ yếu thắng khi sao chép vòng lặp). Tôi đoán lý do là: khi đó là một mảng nhỏ của kiểu nguyên thủy, 'giá để gọi' lớn hơn mức tăng hiệu suất. Việc sử dụng JNI có thể làm giảm hiệu suất vì lý do tương tự - bản thân mã gốc rất nhanh, nhưng việc gọi nó từ một quy trình java - không quá nhiều.

Chỉnh sửa nhỏ, Arrays.copy không phải là JNI, nó là nội tại. Intrinsics nhanh hơn nhiều so với JNI. Trình biên dịch JIT biến nó thành nội tại như thế nào và khi nào tùy thuộc vào trình biên dịch / JVM được sử dụng.
Nitsan Wakart

1
Arrays.copykhông tồn tại, Arrays.copyOflà một chức năng thư viện.
Blaisorblade

12

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 . Trên thực tế, Calibre đi kèm với một số ví dụ, bao gồm một ví dụ 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ủ ảo Java HotSpot (TM) 64-Bit của Oracle, 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, RAM 8 GiB). Tôi không tin rằng việc đăng dữ liệu thời gian thô sẽ 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 một forvòng lặp thủ công để sao chép từng phần tử vào một mảng mới được khởi tạo không bao giờ có lợi, cho dù đối với mảng ngắn hay mảng dài.
  • Arrays.copyOf(array, array.length)array.clone()cả hai đều nhanh chóng. Hai kỹ thuật này gần giống nhau về hiệu suất; 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 bằng và , nhưng không hoàn toàn nhất quán như vậy. (Xem trường hợp trong 50000 s.) Do đó, và tính chi tiết của cuộc gọi, tôi khuyên bạn nên nếu bạn cần kiểm soát tốt các phần tử được sao chép ở đâu.Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

Đây là các âm mưu thời gian:

Thời gian sao chép mảng có độ dài 5 Thời gian sao chép mảng có độ dài 500 Thời gian sao chép mảng có độ dài 50000


3
Có điều gì đó kỳ lạ về việc sao chép int? Có vẻ kỳ lạ là nó sắp xếp sẽ chậm trên các int ở quy mô lớn.
whaleberg

6

Việc thực thi các phương thức gốc giống như Arrays.copyOf(T[], int)có một số chi phí nhưng không có nghĩa là nó không nhanh vì bạn đang thực thi nó bằng JNI.

Cách đơn giản nhất là viết điểm chuẩn và bài kiểm tra.

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

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 các phần của nó), vì vậy nó rất nhanh, như bạn có thể xác nhận tại đây


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 là bất biến
Daren

1
JNI rất chậm . System.arraycopykhông sử dụng nó.
Chai T. Rex

Không, System.arraycopykhông sử dụng JNI, chỉ để gọi các thư viện của bên thứ ba. Thay vào đó, nó là một cuộc gọi riêng, có nghĩa là có một triển khai riêng trong VM cho nó.
Sphereenik

6

Không thể Arrays.copyOfnhanh hơn System.arraycopyvì đây là việc triển khai 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

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


3
Tôi đã đọc rằng đối với các loại không phải là bản địa (bất kỳ lớp nào được tạo, như của tôi) nó có thể không hiệu quả như vậy ... và đối với các kích thước kích thướ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 ... quan tâm đến nhận xét?
Daren

Thật vậy, System.arraycopy () có một số chi phí như vậy cho mảng nhỏ (n = ~ 10) một vòng lặp thực sự là nhanh hơn
RecursiveExceptionException
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.