Apakah lebih baik menggunakan System.arraycopy (…) daripada loop for untuk menyalin array?


90

Saya ingin membuat array baru dari objek yang mengumpulkan dua array yang lebih kecil.

Mereka tidak boleh nol, tetapi ukurannya mungkin 0.

Saya tidak dapat memilih di antara dua cara ini: apakah sama atau lebih efisien (misalnya system.arraycopy () menyalin seluruh bagian)?

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);

atau

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]        
    }
}

Apakah satu-satunya perbedaan tampilan kode?

EDIT: terima kasih untuk pertanyaan terkait, tetapi mereka tampaknya memiliki diskusi yang belum terpecahkan:

Apakah benar-benar lebih cepat if it is not for native types: byte [], Object [], char []? dalam semua kasus lain, pemeriksaan tipe dijalankan, yang akan menjadi kasus saya dan karenanya akan setara ... bukan?

Pada pertanyaan terkait lainnya, mereka mengatakan bahwa the size matters a lot, untuk ukuran> 24 system.arraycopy () menang, untuk lebih kecil dari 10, manual for loop lebih baik ...

Sekarang saya benar-benar bingung.


16
arraycopy()adalah panggilan asli, yang pasti lebih cepat.
Sotirios Delimanolis

4
Sudahkah Anda mencoba membandingkan dua implementasi yang berbeda?
Alex


15
Anda harus memilih mana yang menurut Anda paling mudah dibaca dan paling mudah dikelola di masa mendatang. Hanya jika Anda telah menentukan bahwa ini adalah sumber kemacetan, Anda harus mengubah pendekatan Anda.
arshajii

1
Jangan menemukan kembali roda!
camickr

Jawaban:


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);
}

Saya tahu tes JUnit sebenarnya bukan yang terbaik untuk benchmarking, tetapi
testHardCopyBytes membutuhkan 0,157 detik untuk menyelesaikannya
dan
testArrayCopyBytes membutuhkan 0,086 detik untuk menyelesaikannya.

Saya pikir itu tergantung pada mesin virtual, tetapi sepertinya itu menyalin blok memori daripada menyalin elemen array tunggal. Ini benar-benar akan meningkatkan kinerja.

EDIT:
Sepertinya kinerja System.arraycopy ada di mana-mana. Ketika String digunakan sebagai pengganti byte, dan array kecil (ukuran 10), saya mendapatkan hasil ini:

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

Berikut adalah tampilannya saat array berukuran 0x1000000. Sepertinya System.arraycopy pasti menang dengan array yang lebih besar.

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

Aneh sekali!

Terima kasih, Daren, untuk menunjukkan bahwa salinan referensi berbeda. Itu membuat masalah ini jauh lebih menarik!


2
Terima kasih atas usaha Anda, tetapi Anda melewatkan poin yang tampaknya penting: tipe non-native (buat kelas acak dengan tanda apa pun sehingga array berisi referensi) dan ukuran ... tampaknya untuk ukuran array yang lebih kecil, perulangan manual lebih cepat. Mau memperbaiki ini?
Daren

2
Oh wow, kamu benar! Menarik sekali. Menempatkan String dalam larik tersebut alih-alih dalam byte membuat perbedaan besar: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Trent Small

Hasil apa yang Anda dapatkan? juga menarik untuk dicoba dengan ukuran array = 10 ... terima kasih! (Saya berharap saya memiliki IDE saya di sini, saya membuat kode tanpa kompiler).
Daren

Sekarang saya telah membungkusnya dalam beberapa panggilan System.nanoTime () dan mengatur size = 10 untuk melihat berapa banyak nanodetik yang dibutuhkan masing-masing. Sepertinya untuk array primitif kecil, loop lebih baik; untuk referensi, arrayCopy lebih baik .: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent Kecil

Hasil yang sangat menarik! Terima kasih banyak! bisakah Anda mengedit jawaban Anda untuk menyertakan ini, dan saya dengan senang hati akan menerimanya sehingga tetap menjadi yang pertama dilihat semua orang ... Anda sudah mendapatkan suara saya. :)
Daren

36

Arrays.copyOf(T[], int)lebih mudah dibaca. Internaly yang digunakannya System.arraycopy()adalah panggilan asli.

Anda tidak bisa mendapatkannya lebih cepat!


Sepertinya Anda bisa bergantung pada beberapa hal, tapi terima kasih telah menunjukkan fungsi yang saya tidak tahu dan memang lebih mudah dibaca. :)
Daren

Iya! itu tergantung pada beberapa hal seperti yang dikatakan @Svetoslav Tsolov. saya hanya ingin menunjukkan Arrays.copyOf
Philipp Sander

2
copyOftidak selalu bisa diganti arraycopy, tetapi itu sesuai untuk kasus penggunaan ini.
Blaisorblade

1
NB Jika Anda melihat kinerja maka ini tidak akan secepat System.arraycopy () karena membutuhkan alokasi memori. Jika ini terjadi dalam satu putaran, alokasi berulang akan mengarah pada pengumpulan sampah yang akan berdampak besar pada kinerja.
Akan Calderwood

1
@PhilippSander Hanya untuk memeriksa bahwa saya tidak bodoh, saya menambahkan kode ke salinan array 1MB di loop game saya, yang hampir tidak pernah mengaktifkan GC. Dengan Array.copyOf () DVM saya memanggil GC 5 kali per detik dan game menjadi sangat lamban. Saya pikir aman untuk mengatakan ada alokasi memori yang terjadi.
Akankah Calderwood

17

Itu tergantung pada mesin virtual, tetapi System.arraycopy akan memberi Anda yang terdekat dengan kinerja asli.

Saya telah bekerja selama 2 tahun sebagai pengembang java untuk sistem tertanam (di mana kinerja adalah prioritas besar) dan di mana pun System.arraycopy dapat digunakan, saya sebagian besar telah menggunakannya / melihatnya digunakan dalam kode yang ada. Itu selalu lebih disukai daripada loop ketika kinerja menjadi masalah. Jika kinerja bukan masalah besar, saya akan pergi dengan loop. Jauh lebih mudah dibaca.


Hal-hal seperti ukuran dan jenis array (dasar vs diwariskan) tampaknya memengaruhi dalm.
Daren

2
Ya, ini bukan 'kinerja asli' itu sendiri, itulah sebabnya saya mengatakan bahwa saya 'kebanyakan' menggunakannya di mana saya bisa (Anda akan melihatnya sebagian besar menang atas penyalinan loop). Saya kira alasannya adalah: ketika itu adalah array kecil dari tipe primitif, 'biaya untuk panggilan' lebih besar daripada peningkatan kinerja. Menggunakan JNI dapat menurunkan kinerja karena alasan yang sama - kode asli itu sendiri cepat, tetapi memanggilnya dari proses java - tidak terlalu banyak.

Koreksi kecil, Array.copy bukan JNI, itu intrinsik. Intrinsik jauh lebih cepat daripada JNI. Bagaimana dan kapan kompilator JIT mengubahnya menjadi intrinsik bergantung pada JVM / kompilator yang digunakan.
Nitsan Wakart

1
Arrays.copytidak ada, Arrays.copyOfadalah fungsi perpustakaan.
Blaisorblade

14

Alih-alih mengandalkan spekulasi dan informasi yang mungkin sudah ketinggalan zaman, saya menjalankan beberapa benchmark menggunakan . Faktanya, Caliper hadir dengan beberapa contoh, termasuk CopyArrayBenchmarkyang mengukur pertanyaan ini dengan tepat! Yang harus Anda lakukan hanyalah lari

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

Hasil saya didasarkan pada VM Server 64-Bit Java HotSpot (TM) Oracle, 1.8.0_31-b13, yang berjalan pada MacBook Pro pertengahan 2010 (macOS 10.11.6 dengan Intel Arrandale i7, 8 GiB RAM). Saya tidak percaya bahwa memposting data waktu mentah itu berguna. Sebaliknya, saya akan meringkas kesimpulan dengan visualisasi pendukung.

Singkatnya:

  • Menulis forloop manual untuk menyalin setiap elemen ke dalam array yang baru dibuat tidak pernah menguntungkan, baik untuk array pendek atau array panjang.
  • Arrays.copyOf(array, array.length)dan array.clone()keduanya cepat secara konsisten. Kedua teknik ini hampir identik dalam kinerja; mana yang kamu pilih adalah soal selera.
  • System.arraycopy(src, 0, dest, 0, src.length)hampir secepat dan , tetapi tidak secara konsisten. (Lihat kasus untuk 50000 s.) Karena itu, dan verbositas panggilan, saya akan merekomendasikan jika Anda memerlukan kontrol yang baik atas elemen mana yang akan disalin di mana.Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

Berikut plot waktunya:

Pengaturan waktu untuk menyalin array dengan panjang 5 Pengaturan waktu untuk menyalin array dengan panjang 500 Pengaturan waktu untuk menyalin array dengan panjang 50000


3
Apakah ada yang aneh tentang penyalinan int? Tampaknya aneh bahwa arraycopy akan lambat pada int pada skala besar.
whaleberg

6

Menjalankan metode asli seperti Arrays.copyOf(T[], int)memang memiliki beberapa overhead tetapi itu tidak berarti bahwa itu tidak secepat Anda mengeksekusinya menggunakan JNI.

Cara termudah adalah dengan menulis patokan dan tes.

Anda dapat memeriksa itu Arrays.copyOf(T[], int)lebih cepat dari forloop normal Anda .

Kode patokan dari sini : -

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()menggunakan JNI (Java Native Interface) untuk menyalin larik (atau sebagian darinya), jadi sangat cepat, seperti yang dapat Anda konfirmasikan di sini


Kode ini menggunakan int [] dapatkah Anda mencobanya dengan String [] (diinisialisasi dengan nilai yang berbeda: "1", "2", dll. Karena tidak dapat diubah
Daren

1
JNI sangat lambat . System.arraycopytidak menggunakannya.
Chai T.Rex

Tidak, System.arraycopytidak menggunakan JNI, yang hanya untuk memanggil perpustakaan pihak ketiga. Sebaliknya, ini adalah panggilan asli, yang berarti ada implementasi asli di VM untuknya.
spheenik

6

Tidak mungkin itu Arrays.copyOflebih cepat daripada System.arraycopykarena ini adalah implementasi dari 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()adalah panggilan asli yang melakukan operasi penyalinan langsung di memori. Salinan memori tunggal akan selalu lebih cepat daripada for loop Anda


3
Saya telah membaca bahwa untuk tipe non-native (kelas yang dibuat, seperti milik saya) mungkin tidak begitu efisien ... dan untuk ukuran kecil (kasus saya) manual untuk loop bisa lebih baik ... peduli untuk berkomentar?
Daren

Memang, System.arraycopy () memiliki beberapa overhead sehingga untuk array kecil (n = ~ 10) loop sebenarnya lebih cepat
RecursiveExceptionException
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.