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!