Saya menyelidiki hotspot kinerja dalam sebuah aplikasi yang menghabiskan 50% waktunya di memmove (3). Aplikasi ini memasukkan jutaan integer 4-byte ke dalam array yang diurutkan, dan menggunakan memmove untuk menggeser data "ke kanan" untuk memberi ruang bagi nilai yang disisipkan.
Harapan saya adalah menyalin memori sangat cepat, dan saya terkejut bahwa begitu banyak waktu dihabiskan untuk memmove. Tapi kemudian saya mendapat ide bahwa memmove lambat karena memindahkan daerah yang tumpang tindih, yang harus diimplementasikan dalam loop yang ketat, daripada menyalin halaman memori yang besar. Saya menulis sebuah microbenchmark kecil untuk mengetahui apakah ada perbedaan kinerja antara memcpy dan memmove, mengharapkan memcpy menang telak.
Saya menjalankan benchmark saya pada dua mesin (core i5, core i7) dan melihat bahwa memmove sebenarnya lebih cepat daripada memcpy, pada core i7 yang lebih lama bahkan hampir dua kali lebih cepat! Sekarang saya mencari penjelasan.
Ini patokan saya. Ini menyalin 100 mb dengan memcpy, dan kemudian memindahkan sekitar 100 mb dengan memmove; sumber dan tujuan tumpang tindih. Berbagai "jarak" untuk sumber dan tujuan dicoba. Setiap tes dijalankan 10 kali, waktu rata-rata dicetak.
https://gist.github.com/cruppstahl/78a57cdf937bca3d062c
Berikut adalah hasil pada Core i5 (Linux 3.5.0-54-generic # 81 ~ precision1-Ubuntu SMP x86_64 GNU / Linux, gcc adalah 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5). Angka dalam tanda kurung adalah jarak (ukuran celah) antara sumber dan tujuan:
memcpy 0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633
Memmove diimplementasikan sebagai kode assembler yang dioptimalkan SSE, menyalin dari belakang ke depan. Ia menggunakan perangkat keras prefetch untuk memuat data ke dalam cache, dan menyalin 128 byte ke register XMM, kemudian menyimpannya di tujuan.
( memcpy-ssse3-back.S , baris 1650 ff)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub $0x80, %rdx
movdqu -0x10(%rsi), %xmm1
movdqu -0x20(%rsi), %xmm2
movdqu -0x30(%rsi), %xmm3
movdqu -0x40(%rsi), %xmm4
movdqu -0x50(%rsi), %xmm5
movdqu -0x60(%rsi), %xmm6
movdqu -0x70(%rsi), %xmm7
movdqu -0x80(%rsi), %xmm8
movdqa %xmm1, -0x10(%rdi)
movdqa %xmm2, -0x20(%rdi)
movdqa %xmm3, -0x30(%rdi)
movdqa %xmm4, -0x40(%rdi)
movdqa %xmm5, -0x50(%rdi)
movdqa %xmm6, -0x60(%rdi)
movdqa %xmm7, -0x70(%rdi)
movdqa %xmm8, -0x80(%rdi)
lea -0x80(%rsi), %rsi
lea -0x80(%rdi), %rdi
jae L(gobble_ll_loop)
Mengapa memmove lebih cepat daripada memcpy? Saya berharap memcpy menyalin halaman memori, yang seharusnya jauh lebih cepat daripada perulangan. Dalam kasus terburuk, saya mengharapkan memcpy menjadi secepat memmove.
PS: Saya tahu bahwa saya tidak dapat mengganti memmove dengan memcpy di kode saya. Saya tahu bahwa sampel kode mencampurkan C dan C ++. Pertanyaan ini sebenarnya hanya untuk tujuan akademis.
UPDATE 1
Saya menjalankan beberapa variasi tes, berdasarkan berbagai jawaban.
- Saat menjalankan memcpy dua kali, maka proses kedua lebih cepat dari yang pertama.
- Ketika "menyentuh" buffer tujuan dari memcpy (
memset(b2, 0, BUFFERSIZE...)
) maka menjalankan memcpy yang pertama juga lebih cepat. - memcpy masih sedikit lebih lambat dari memmove.
Berikut hasilnya:
memcpy 0.0118526
memcpy 0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648
Kesimpulan saya: berdasarkan komentar dari @Oliver Charlesworth, sistem operasi harus melakukan memori fisik segera setelah buffer tujuan memcpy diakses untuk pertama kalinya (jika ada yang tahu cara "membuktikan" ini, tambahkan jawaban! ). Selain itu, seperti yang dikatakan @Mats Petersson, memmove lebih ramah cache daripada memcpy.
Terima kasih atas semua jawaban dan komentar yang bagus!
sumber
memmove
. Cabang ini tidak dapat menangani perpindahan ketika sumber tumpang tindih dengan tujuan dan tujuan berada di alamat yang lebih rendah.memcpy
loop Anda adalah pertama kalinya kontenb2
diakses, sehingga OS harus menggunakan memori fisik untuk itu saat berjalan.Jawaban:
memmove
Panggilan Anda mengacak memori sebesar 2 hingga 128 byte, sementaramemcpy
sumber dan tujuan Anda sama sekali berbeda. Entah bagaimana itu menjelaskan perbedaan kinerja: jika Anda menyalin ke tempat yang sama, Anda akan melihatmemcpy
kemungkinan hasil yang sedikit lebih cepat, misalnya di ideone.com :memmove (002) 0.0610362 memmove (004) 0.0554264 memmove (008) 0.0575859 memmove (016) 0.057326 memmove (032) 0.0583542 memmove (064) 0.0561934 memmove (128) 0.0549391 memcpy 0.0537919
Hampir tidak ada apa pun di dalamnya - tidak ada bukti bahwa menulis kembali ke halaman memori yang sudah salah memiliki banyak dampak, dan kami tentu saja tidak melihat separuh waktu ... tetapi itu menunjukkan bahwa tidak ada yang salah membuat
memcpy
lebih lambat yang tidak perlu jika dibandingkan dengan apel -untuk-apel.sumber
memcpy
dulu lagi?Saat Anda menggunakan
memcpy
, penulisan harus masuk ke cache. Saat Anda menggunakanmemmove
tempat saat Anda menyalin langkah kecil ke depan, memori yang Anda salin akan sudah berada di cache (karena terbaca 2, 4, 16 atau 128 byte "kembali"). Coba lakukan dimemmove
mana tujuannya adalah beberapa megabyte (> 4 * ukuran cache), dan saya curiga (tetapi tidak dapat diganggu untuk menguji) bahwa Anda akan mendapatkan hasil yang serupa.Saya menjamin bahwa SEMUA adalah tentang pemeliharaan cache ketika Anda melakukan operasi memori besar.
sumber
memcpy
akan menjadi lebih cepat karena TLB sudah diisi sebelumnya. Selain itu, sedetikmemcpy
pun tidak perlu mengosongkan cache dari hal-hal yang mungkin perlu Anda "singkirkan" (cache-lines yang kotor "buruk" untuk kinerja dalam banyak hal. Namun, untuk memastikannya, Anda harus menjalankan sesuatu seperti "perf" dan contoh hal-hal seperti cache-miss, TLB miss, dan sebagainya.Secara historis, memmove dan memcopy adalah fungsi yang sama. Mereka bekerja dengan cara yang sama dan memiliki implementasi yang sama. Kemudian disadari bahwa memcopy tidak perlu (dan sering tidak) didefinisikan untuk menangani area yang tumpang tindih dengan cara tertentu.
Hasil akhirnya adalah memmove didefinisikan untuk menangani wilayah yang tumpang tindih dengan cara tertentu meskipun hal ini memengaruhi kinerja. Memcopy seharusnya menggunakan algoritme terbaik yang tersedia untuk wilayah yang tidak tumpang tindih. Penerapannya biasanya hampir identik.
Masalah yang Anda hadapi adalah ada begitu banyak variasi dari perangkat keras x86 sehingga tidak mungkin untuk mengetahui metode mana untuk memindahkan memori yang akan menjadi yang tercepat. Dan bahkan jika Anda merasa mendapatkan hasil dalam satu keadaan, sesuatu yang sederhana seperti memiliki 'langkah' yang berbeda dalam tata letak memori dapat menyebabkan kinerja cache yang sangat berbeda.
Anda dapat menentukan apa yang sebenarnya Anda lakukan atau mengabaikan masalah dan mengandalkan tolok ukur yang dilakukan untuk pustaka C.
Edit: Oh, dan satu hal terakhir; memindahkan banyak konten memori SANGAT lambat. Saya kira aplikasi Anda akan berjalan lebih cepat dengan sesuatu seperti implementasi B-Tree sederhana untuk menangani integer Anda. (Oh kamu, oke)
Sunting2: Untuk meringkas ekspansi saya di komentar: Microbenchmark adalah masalahnya di sini, ini tidak mengukur apa yang Anda pikirkan. Tugas yang diberikan untuk memcpy dan memmove sangat berbeda satu sama lain. Jika tugas yang diberikan ke memcpy diulangi beberapa kali dengan memmove atau memcpy hasil akhirnya tidak akan bergantung pada fungsi perpindahan memori yang Anda gunakan KECUALI jika regionnya tumpang tindih.
sumber
"memcpy lebih efisien daripada memmove." Dalam kasus Anda, kemungkinan besar Anda tidak melakukan hal yang persis sama saat menjalankan kedua fungsi tersebut.
Secara umum, GUNAKAN memmove hanya jika perlu. GUNAKAN jika terdapat kemungkinan yang sangat wajar bahwa wilayah sumber dan tujuan bertumpuk.
Referensi: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Kuliah Sistem Intro Stanford - 7) Waktu: 36:00
sumber