Apakah lebih baik digunakan memcpy
seperti yang ditunjukkan di bawah ini atau lebih baik digunakan std::copy()
dalam hal kinerja? Mengapa?
char *bits = NULL;
...
bits = new (std::nothrow) char[((int *) copyMe->bits)[0]];
if (bits == NULL)
{
cout << "ERROR Not enough memory.\n";
exit(1);
}
memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);
c++
performance
optimization
pengguna576670
sumber
sumber
char
dapat ditandatangani atau tidak ditandatangani, tergantung pada implementasinya. Jika jumlah byte bisa> = 128, maka gunakanunsigned char
untuk array byte Anda. (Para(int *)
pemain juga akan lebih aman(unsigned int *)
.)std::vector<char>
? Atau karena Anda mengatakanbits
,std::bitset
?(int*) copyMe->bits[0]
terjadi?int
menentukan ukurannya, tapi itu sepertinya resep untuk implementasi bencana yang ditentukan, seperti banyak hal lain di sini.(int *)
pemeran itu hanyalah perilaku murni yang tidak terdefinisi, bukan implementasi-didefinisikan. Mencoba melakukan jenis-hukuman melalui para pemain melanggar aturan alias yang ketat dan karenanya benar-benar tidak ditentukan oleh Standar. (Juga, dalam C ++ walaupun bukan C, Anda tidak dapat mengetik-pun melaluiunion
salah satu dari keduanya.) Satu-satunya pengecualian adalah jika Anda mengonversi ke varianchar*
, tetapi kelonggarannya tidak simetris.Jawaban:
Saya akan menentang kebijaksanaan umum di sini yang
std::copy
akan memiliki sedikit kehilangan kinerja, hampir tak terlihat. Saya hanya melakukan tes dan menemukan bahwa itu tidak benar: Saya memang melihat perbedaan kinerja. Namun, pemenangnya adalahstd::copy
.Saya menulis implementasi C ++ SHA-2. Dalam pengujian saya, saya hash 5 string menggunakan keempat versi SHA-2 (224, 256, 384, 512), dan saya loop 300 kali. Saya mengukur waktu menggunakan Boost.timer. 300 loop counter itu cukup untuk sepenuhnya menstabilkan hasil saya. Saya menjalankan tes masing-masing 5 kali, bergantian antara
memcpy
versi danstd::copy
versi. Kode saya mengambil keuntungan dari mengambil data dalam potongan sebanyak mungkin (banyak implementasi lain beroperasi denganchar
/char *
, sedangkan saya beroperasi denganT
/T *
(di manaT
adalah tipe terbesar dalam implementasi pengguna yang memiliki perilaku overflow yang benar), sehingga akses memori cepat pada tipe terbesar yang saya dapat adalah pusat kinerja algoritma saya. Ini adalah hasil saya:Waktu (dalam detik) untuk menyelesaikan uji SHA-2
Total peningkatan rata-rata kecepatan std :: copy over memcpy: 2.99%
Kompiler saya adalah gcc 4.6.3 pada Fedora 16 x86_64. Bendera pengoptimalan saya adalah
-Ofast -march=native -funsafe-loop-optimizations
.Kode untuk implementasi SHA-2 saya.
Saya memutuskan untuk menjalankan tes pada implementasi MD5 saya juga. Hasilnya jauh lebih tidak stabil, jadi saya memutuskan untuk melakukan 10 kali. Namun, setelah beberapa upaya pertama saya, saya mendapatkan hasil yang sangat bervariasi dari satu lari ke yang berikutnya, jadi saya menduga ada semacam aktivitas OS yang terjadi. Saya memutuskan untuk memulai dari awal.
Pengaturan dan bendera kompiler yang sama. Hanya ada satu versi MD5, dan ini lebih cepat dari SHA-2, jadi saya melakukan 3000 loop pada set yang sama dari 5 string tes.
Ini adalah 10 hasil akhir saya:
Waktu (dalam detik) untuk menyelesaikan tes MD5
Total penurunan rata-rata kecepatan std :: copy over memcpy: 0.11%
Kode untuk implementasi MD5 saya
Hasil ini menunjukkan bahwa ada beberapa optimasi yang std :: copy digunakan dalam tes SHA-2 saya yang
std::copy
tidak dapat digunakan dalam tes MD5 saya. Dalam tes SHA-2, kedua array diciptakan dalam fungsi yang sama yang disebutstd::copy
/memcpy
. Dalam tes MD5 saya, salah satu array diteruskan ke fungsi sebagai parameter fungsi.Saya melakukan sedikit pengujian lagi untuk melihat apa yang bisa saya lakukan untuk membuat
std::copy
lebih cepat lagi. Jawabannya ternyata sederhana: aktifkan optimasi waktu tautan. Ini adalah hasil saya dengan LTO dihidupkan (opsi -flto di gcc):Waktu (dalam detik) untuk menyelesaikan uji MD5 dengan -flto
Total peningkatan rata-rata kecepatan std :: copy over memcpy: 0.72%
Singkatnya, tampaknya tidak ada penalti kinerja untuk menggunakan
std::copy
. Bahkan, tampaknya ada peningkatan kinerja.Penjelasan hasil
Jadi mengapa mungkin
std::copy
memberikan peningkatan kinerja?Pertama, saya tidak berharap itu lebih lambat untuk implementasi apa pun, selama optimasi inlining dihidupkan. Semua penyusun inline secara agresif; itu mungkin optimasi yang paling penting karena memungkinkan banyak optimasi lainnya.
std::copy
dapat (dan saya menduga semua implementasi dunia nyata lakukan) mendeteksi bahwa argumen sepele disalin dan bahwa memori diletakkan secara berurutan. Ini berarti bahwa dalam kasus terburuk, ketikamemcpy
legal,std::copy
harus melakukan tidak lebih buruk. Implementasi sepele daristd::copy
yangmemcpy
harus memenuhi kriteria kompiler Anda "selalu sebaris ini ketika mengoptimalkan untuk kecepatan atau ukuran".Namun,
std::copy
juga menyimpan lebih banyak informasinya. Saat Anda meneleponstd::copy
, fungsi tersebut menjaga jenisnya tetap utuh.memcpy
beroperasi padavoid *
, yang membuang hampir semua informasi yang berguna. Sebagai contoh, jika saya memasukkan arraystd::uint64_t
, kompiler atau pelaksana perpustakaan mungkin dapat memanfaatkan keselarasan 64-bit denganstd::copy
, tetapi mungkin lebih sulit untuk melakukannya denganmemcpy
. Banyak implementasi algoritma seperti ini bekerja dengan terlebih dahulu mengerjakan bagian yang tidak selaras di awal rentang, kemudian bagian yang disejajarkan, kemudian bagian yang tidak selaras di bagian akhir. Jika semuanya dijamin akan disejajarkan, maka kode menjadi lebih sederhana dan lebih cepat, dan lebih mudah bagi prediktor cabang dalam prosesor Anda untuk mendapatkan yang benar.Optimalisasi prematur?
std::copy
berada dalam posisi yang menarik. Saya berharap itu tidak pernah lebih lambat darimemcpy
dan kadang-kadang lebih cepat dengan kompiler optimisasi modern. Apalagi apa pun yang Anda bisamemcpy
, Anda bisastd::copy
.memcpy
tidak memungkinkan tumpang tindih dalam buffer, sedangkanstd::copy
dukungan tumpang tindih dalam satu arah (denganstd::copy_backward
untuk arah lain tumpang tindih).memcpy
hanya bekerja pada pointer,std::copy
bekerja pada setiap iterator (std::map
,std::vector
,std::deque
, atau sendiri jenis kustom saya). Dengan kata lain, Anda hanya perlu menggunakanstd::copy
saat Anda perlu menyalin potongan data di sekitar.sumber
std::copy
2,99% atau 0,72% atau -0,11% lebih cepat daripadamemcpy
, kali ini adalah untuk seluruh program untuk dijalankan. Namun, saya biasanya merasa bahwa tolok ukur dalam kode nyata lebih berguna daripada tolok ukur dalam kode palsu. Seluruh program saya mendapat perubahan kecepatan eksekusi. Efek nyata dari hanya dua skema penyalinan akan memiliki perbedaan yang lebih besar daripada yang ditunjukkan di sini ketika diambil secara terpisah, tetapi ini menunjukkan bahwa mereka dapat memiliki perbedaan yang terukur dalam kode aktual.memcpy
danstd::copy
memiliki implementasi yang berbeda, jadi dalam beberapa kasus kompiler mengoptimalkan kode sekitar dan kode salinan memori yang sebenarnya sebagai bagian integral dari kode. Dengan kata lain kadang-kadang satu lebih baik daripada yang lain dan bahkan dengan kata lain, memutuskan mana yang akan digunakan adalah optimasi prematur atau bahkan bodoh, karena dalam setiap situasi Anda harus melakukan penelitian baru dan, terlebih lagi, program biasanya sedang dikembangkan, jadi setelah beberapa perubahan kecil keuntungan dari fungsi di atas yang lain mungkin hilang.std::copy
adalah fungsi inline sepele yang hanya memanggilmemcpy
ketika itu legal. Inlining dasar akan menghilangkan perbedaan kinerja negatif. Saya akan memperbarui posting dengan sedikit penjelasan tentang mengapa std :: copy mungkin lebih cepat.Semua kompiler yang saya tahu akan mengganti yang sederhana
std::copy
dengan yangmemcpy
tepat, atau bahkan lebih baik, membuat salinan vektor sehingga akan lebih cepat dari amemcpy
.Dalam kasus apa pun: profil dan cari tahu sendiri. Kompiler yang berbeda akan melakukan hal yang berbeda, dan sangat mungkin itu tidak akan melakukan persis apa yang Anda minta.
Lihat presentasi ini tentang optimisasi kompiler (pdf).
Inilah yang dilakukan GCC untuk
std::copy
tipe POD sederhana .Inilah pembongkaran (dengan hanya
-O
optimasi), menunjukkan panggilan kememmove
:Jika Anda mengubah fungsi tanda tangan ke
kemudian
memmove
menjadimemcpy
untuk sedikit peningkatan kinerja. Perhatikan bahwamemcpy
itu sendiri akan sangat vektor.sumber
memmove
seharusnya tidak lebih cepat - melainkan harus lebih lambat karena harus memperhitungkan kemungkinan bahwa kedua rentang data tumpang tindih. Saya pikirstd::copy
memungkinkan data yang tumpang tindih, dan karenanya harus meneleponmemmove
.memcpy
. Ini membuat saya percaya bahwa GCC memeriksa apakah ada memori yang tumpang tindih.std::copy
memungkinkan tumpang tindih dalam satu arah tetapi tidak yang lain. Awal dari output tidak dapat terletak di dalam rentang input, tetapi awal dari input diizinkan untuk berada di dalam rentang output. Ini agak aneh, karena urutan tugas ditentukan, dan panggilan mungkin UB meskipun efek dari tugas-tugas itu, dalam urutan itu, didefinisikan. Tapi saya kira pembatasan memungkinkan optimasi vektorisasi.Selalu gunakan
std::copy
karenamemcpy
terbatas hanya pada struktur POD gaya C, dan kompiler kemungkinan akan mengganti panggilanstd::copy
denganmemcpy
jika target sebenarnya adalah POD.Plus,
std::copy
dapat digunakan dengan banyak jenis iterator, bukan hanya pointer.std::copy
lebih fleksibel tanpa kehilangan kinerja dan merupakan pemenang yang jelas.sumber
std::copy(container.begin(), container.end(), destination);
akan menyalin kontencontainer
(semuanya antarabegin
danend
) ke buffer yang ditunjukkan olehdestination
.std::copy
tidak memerlukan shenanigans seperti&*container.begin()
atau&container.back() + 1
.Secara teori,
memcpy
mungkin memiliki keunggulan kinerja yang sedikit , tidak terlihat , sangat kecil , hanya karena tidak memiliki persyaratan yang samastd::copy
. Dari halaman manual darimemcpy
:Dengan kata lain,
memcpy
bisa mengabaikan kemungkinan data yang tumpang tindih. (Melewati array yang tumpang tindihmemcpy
adalah perilaku yang tidak terdefinisi.) Jadimemcpy
tidak perlu memeriksa kondisi ini secara eksplisit, sedangkanstd::copy
dapat digunakan selamaOutputIterator
parameter tidak dalam kisaran sumber. Perhatikan ini tidak sama dengan mengatakan bahwa jangkauan sumber dan jangkauan tujuan tidak dapat tumpang tindih.Jadi karena
std::copy
memiliki persyaratan yang agak berbeda, secara teori seharusnya sedikit (dengan penekanan ekstrem pada sedikit ) lebih lambat, karena mungkin akan memeriksa tumpang tindih array-C, atau mendelegasikan penyalinan array-C kememmove
, yang perlu melakukan memeriksa. Namun dalam praktiknya, Anda (dan sebagian besar profiler) mungkin bahkan tidak akan mendeteksi perbedaan apa pun.Tentu saja, jika Anda tidak bekerja dengan POD , Anda tidak dapat menggunakannya
memcpy
.sumber
std::copy<char>
. Tetapistd::copy<int>
dapat berasumsi bahwa inputnya selaras. Itu akan membuat perbedaan yang jauh lebih besar, karena itu mempengaruhi setiap elemen. Tumpang tindih adalah pemeriksaan satu kali.memcpy
saya lihat memeriksa penyelarasan dan mencoba untuk menyalin kata-kata daripada byte demi byte.memcpy
antarmuka itu kehilangan informasi penyelarasan. Oleh karena itu,memcpy
harus melakukan pemeriksaan pelurusan pada saat run-time untuk menangani awal dan akhir yang tidak selaras. Cek itu mungkin murah tetapi tidak gratis. Sedangkanstd::copy
dapat menghindari pemeriksaan ini dan membuat vektor. Juga, kompiler dapat membuktikan bahwa array sumber dan tujuan tidak tumpang tindih dan vektorisasi lagi tanpa pengguna harus memilih antaramemcpy
danmemmove
.Aturan saya sederhana. Jika Anda menggunakan C ++ lebih suka perpustakaan C ++ dan bukan C :)
sumber
std::end(c_arr)
alih-alihc_arr + i_hope_this_is_the_right_number_of elements
lebih aman? dan mungkin yang lebih penting, lebih jelas. Dan itu akan menjadi poin yang saya tekankan dalam kasus khusus ini:std::copy()
lebih idiomatis, lebih dapat dipelihara jika jenis iterator berubah nanti, mengarah pada sintaks yang lebih jelas, dll.std::copy
lebih aman karena dengan benar menyalin data yang dikirimkan jika mereka bukan tipe POD.memcpy
akan dengan senang hati menyalinstd::string
objek ke representasi byte baru demi byte.Hanya tambahan kecil: Perbedaan kecepatan antara
memcpy()
danstd::copy()
dapat sedikit bervariasi tergantung pada apakah optimasi diaktifkan atau dinonaktifkan. Dengan g ++ 6.2.0 dan tanpa optimasimemcpy()
jelas menang:Ketika optimisasi diaktifkan (
-O3
), semuanya terlihat hampir sama lagi:Semakin besar array, semakin sedikit efek yang didapatnya, tetapi bahkan pada
N=1000
memcpy()
sekitar dua kali lebih cepat ketika optimasi tidak diaktifkan.Kode sumber (memerlukan Google Benchmark):
sumber
Jika Anda benar-benar membutuhkan kinerja penyalinan maksimum (yang mungkin tidak Anda), gunakan keduanya .
Ada banyak yang dapat dilakukan untuk mengoptimalkan penyalinan memori - bahkan lebih jika Anda bersedia menggunakan beberapa utas / inti untuk itu. Lihat, misalnya:
Apa yang hilang / kurang optimal dalam implementasi memcpy ini?
baik pertanyaan dan beberapa jawaban telah menyarankan implementasi atau tautan ke implementasi.
sumber
Profiling menunjukkan pernyataan itu:
std::copy()
selalu secepatmemcpy()
atau lebih cepat salah.Sistem saya:
Kode (bahasa: c ++):
Red Alert menunjukkan bahwa kode tersebut menggunakan memcpy dari array ke array dan std :: copy dari array ke vektor. Itu bisa menjadi alasan untuk memcpy lebih cepat.
Karena ada
v.reserve (sizeof (arr1));
tidak akan ada perbedaan dalam salinan ke vektor atau array.
Kode diperbaiki untuk menggunakan array untuk kedua kasus. memcpy masih lebih cepat:
sumber
std::copy
dari vektor ke array entah bagaimana membuatmemcpy
butuh waktu hampir dua kali lebih lama? Data ini sangat mencurigakan. Saya mengkompilasi kode Anda menggunakan gcc dengan -O3, dan perakitan yang dihasilkan sama untuk kedua loop. Jadi setiap perbedaan waktu yang Anda amati pada mesin Anda hanya bersifat insidental.