Loop Anda hanya menyalin satu lokasi. Saya pikir Anda entah bagaimana bermaksud untuk meningkatkan petunjuk.
Mysticial
13
Atau, Anda bisa memperbaikinya untuk mereka, seperti yang saya lakukan. Dan, BTW, tidak ada programmer C sejati yang pernah menghitung dari 1hingga N, selalu dari 0hingga N-1:-)
paxdiablo
6
@paxdiablo: Jika Anda mengulang array, tentu. Tetapi ada banyak kasus di mana perulangan dari 1 ke N baik-baik saja. Bergantung pada apa yang Anda lakukan dengan data - jika Anda menampilkan daftar bernomor mulai dari 1, misalnya, kepada pengguna, maka mulai dari 1 mungkin lebih masuk akal. Bagaimanapun, ini mengabaikan masalah yang lebih besar yang digunakan intsebagai penghitung ketika tipe unsigned like size_tharus digunakan sebagai gantinya.
Billy ONeal
2
@paxdiablo Anda juga dapat menghitung dari N ke 1. Pada beberapa prosesor yang akan menghilangkan satu instruksi pembanding karena decrement akan menyetel bit yang sesuai untuk instruksi cabang ketika mencapai nol.
onemasse
6
Saya pikir premis pertanyaan itu salah. Kompiler modern akan mengubahnya menjadi memcpyatau memmove(tergantung pada apakah mereka dapat mengetahui apakah pointernya mungkin alias).
David Schwartz
Jawaban:
120
Karena memcpy menggunakan penunjuk kata daripada pengarah byte, implementasi memcpy juga sering ditulis dengan instruksi SIMD yang memungkinkan untuk mengacak 128 bit pada satu waktu.
Instruksi SIMD adalah instruksi perakitan yang dapat melakukan operasi yang sama pada setiap elemen dalam vektor hingga panjang 16 byte. Itu termasuk memuat dan menyimpan instruksi.
Saat Anda mengaktifkan GCC -O3, itu akan menggunakan SIMD untuk loop, setidaknya jika ia tahu pDestdan pSrctidak alias.
Dietrich Epp
Saat ini saya sedang mengerjakan Xeon Phi dengan 64 byte (512 bit) SIMD, jadi hal "hingga 16 byte" ini membuat saya tersenyum. Selain itu, Anda harus menentukan CPU yang Anda targetkan agar SIMD dapat diaktifkan, misalnya dengan -march = native.
yakoudbz
Mungkin saya harus merevisi jawaban saya. :)
onemasse
Ini sangat ketinggalan jaman bahkan pada saat posting. Vektor AVX pada x86 (dikirim pada tahun 2011) berukuran 32 byte, dan AVX-512 berukuran 64 byte. Ada beberapa arsitektur dengan vektor 1024-bit atau 2048-bit, atau bahkan lebar vektor variabel seperti ARM SVE
phuclv
@phuclv sementara instruksinya mungkin telah tersedia, apakah Anda memiliki bukti bahwa memcpy menggunakannya? Biasanya diperlukan beberapa saat untuk perpustakaan untuk mengejar ketinggalan, dan yang terbaru dapat saya temukan menggunakan SSSE3 dan jauh lebih baru dari 2011.
Pete Kirkham
81
Rutinitas penyalinan memori bisa jauh lebih rumit dan lebih cepat daripada penyalinan memori sederhana melalui petunjuk seperti:
void simple_memory_copy(void* dst,void* src,unsignedint bytes){unsignedchar* b_dst =(unsignedchar*)dst;unsignedchar* b_src =(unsignedchar*)src;for(int i =0; i < bytes;++i)*b_dst++=*b_src++;}
Perbaikan
Perbaikan pertama yang dapat dilakukan seseorang adalah menyelaraskan salah satu petunjuk pada batas kata (menurut kata yang saya maksud adalah ukuran integer asli, biasanya 32 bit / 4 byte, tetapi bisa 64 bit / 8 byte pada arsitektur yang lebih baru) dan menggunakan gerakan ukuran kata / salin instruksi. Ini membutuhkan penggunaan salinan byte ke byte sampai pointer sejajar.
Arsitektur yang berbeda akan bekerja secara berbeda berdasarkan apakah sumber atau penunjuk tujuan disejajarkan dengan tepat. Misalnya pada prosesor XScale saya mendapatkan kinerja yang lebih baik dengan menyelaraskan penunjuk tujuan daripada penunjuk sumber.
Untuk lebih meningkatkan kinerja, beberapa loop unrolling dapat dilakukan, sehingga lebih banyak register prosesor yang dimuat dengan data dan itu berarti instruksi muat / penyimpanan dapat disisipkan dan latensinya disembunyikan oleh instruksi tambahan (seperti penghitungan loop, dll). Manfaat yang dibawa ini sedikit berbeda oleh prosesor, karena latensi instruksi muat / penyimpanan bisa sangat berbeda.
Pada tahap ini kode akhirnya ditulis dalam Assembly daripada C (atau C ++) karena Anda perlu menempatkan instruksi pemuatan dan penyimpanan secara manual untuk mendapatkan manfaat maksimal dari penyembunyian latensi dan throughput.
Umumnya, seluruh baris data cache harus disalin dalam satu iterasi dari loop yang tidak digulung.
Yang membawa saya ke peningkatan berikutnya, menambahkan pengambilan awal. Ini adalah instruksi khusus yang memberi tahu sistem cache prosesor untuk memuat bagian tertentu dari memori ke dalam cache-nya. Karena ada penundaan antara mengeluarkan instruksi dan mengisi baris cache, instruksi perlu ditempatkan sedemikian rupa sehingga data tersedia ketika akan disalin, dan tidak cepat / lambat.
Ini berarti meletakkan instruksi prefetch di awal fungsi serta di dalam loop salinan utama. Dengan instruksi prefetch di tengah-tengah loop salinan mengambil data yang akan disalin dalam beberapa waktu iterasi.
Saya tidak dapat mengingatnya, tetapi mungkin juga bermanfaat untuk mengambil lebih dulu alamat tujuan serta alamat sumber.
Faktor
Faktor utama yang mempengaruhi seberapa cepat memori dapat disalin adalah:
Jadi, jika Anda ingin menulis rutinitas mengatasi memori yang efisien dan cepat, Anda harus mengetahui cukup banyak tentang prosesor dan arsitektur yang Anda tulis. Cukuplah untuk mengatakan, kecuali Anda menulis pada beberapa platform tertanam, akan jauh lebih mudah untuk hanya menggunakan rutinitas penyalinan memori bawaan.
CPU modern akan mendeteksi pola akses memori linier dan mulai mengambil sendiri. Saya berharap instruksi prefetch tidak akan membuat banyak perbedaan karena itu.
maksimal
@maxy Pada beberapa arsitektur yang telah saya implementasikan, rutinitas penyalinan memori menambahkan prefetch telah membantu secara terukur. Meskipun mungkin benar bahwa chip Intel / AMD generasi saat ini melakukan prefetch cukup jauh ke depan, ada banyak chip yang lebih tua dan arsitektur lain yang tidak.
Daemin
adakah yang bisa menjelaskan "(b_src & 0x3)! = 0"? Saya tidak bisa memahaminya, dan juga - itu tidak akan dikompilasi (melempar kesalahan: operator tidak valid ke biner &: unsigned char dan int);
David Refaeli
"(b_src & 0x3)! = 0" memeriksa apakah 2 bit terendah bukan 0. Jadi jika penunjuk sumber disejajarkan dengan kelipatan 4 byte atau tidak. Kesalahan kompilasi Anda terjadi karena memperlakukan 0x3 sebagai byte bukan masuk, Anda dapat memperbaikinya dengan menggunakan 0x00000003 atau 0x3i (menurut saya).
Daemin
b_src & 0x3tidak akan dikompilasi karena Anda tidak diizinkan melakukan aritmatika bitwise pada jenis penunjuk. Anda harus mentransmisikannya (u)intptr_tterlebih dahulu
phuclv
18
memcpydapat menyalin lebih dari satu byte sekaligus tergantung pada arsitektur komputer. Kebanyakan komputer modern dapat bekerja dengan 32 bit atau lebih dalam satu instruksi prosesor.
00026 * Untuk penyalinan cepat, optimalkan kasus umum di mana kedua penunjuk
00027 * dan panjangnya disejajarkan dengan kata, dan salin kata-pada-waktu sebagai gantinya
00028 * byte-at-a-time. Jika tidak, salin per byte.
Pada 386 (sebagai contoh), yang tidak memiliki cache on-board, hal ini membuat perbedaan besar. Pada kebanyakan prosesor modern, pembacaan dan penulisan akan terjadi satu baris cache pada satu waktu, dan bus ke memori biasanya akan menjadi penghambat, jadi harapkan peningkatan beberapa persen, bukan mendekati empat kali lipat.
Jerry Coffin
2
Saya pikir Anda harus sedikit lebih eksplisit saat mengatakan "dari sumbernya". Tentu, itu adalah "sumber" pada beberapa arsitektur, tetapi jelas bukan pada, katakanlah, mesin BSD atau Windows. (Dan sih, bahkan di antara sistem GNU sering kali ada banyak perbedaan dalam fungsi ini)
Billy ONeal
@Billy ONeal: +1 benar sekali ... ada lebih dari satu cara untuk menguliti kucing. Itu hanya satu contoh. Tetap! Terima kasih atas komentar konstruktifnya.
Mark Byers
7
Anda dapat mengimplementasikan memcpy()menggunakan salah satu teknik berikut, beberapa bergantung pada arsitektur Anda untuk peningkatan performa, dan semuanya akan jauh lebih cepat daripada kode Anda:
Gunakan unit yang lebih besar, seperti kata 32-bit, bukan byte. Anda juga dapat (atau mungkin harus) berurusan dengan penyelarasan di sini juga. Anda tidak dapat membaca / menulis kata 32-bit ke lokasi memori yang aneh misalnya di beberapa platform, dan di platform lain Anda membayar penalti performa yang sangat besar. Untuk mengatasinya, alamatnya harus berupa unit yang dapat dibagi 4. Anda dapat menggunakan hingga 64-bit untuk 64-bit CPU, atau bahkan lebih tinggi menggunakan instruksi SIMD (Instruksi tunggal, banyak data) ( MMX , SSE , dll.)
Anda dapat menggunakan instruksi CPU khusus yang kompilator Anda mungkin tidak dapat mengoptimalkan dari C. Misalnya, pada 80386, Anda dapat menggunakan instruksi awalan "rep" + instruksi "movsb" untuk memindahkan N byte yang ditentukan dengan menempatkan N dalam hitungan daftar. Kompiler yang baik hanya akan melakukan ini untuk Anda, tetapi Anda mungkin berada pada platform yang tidak memiliki kompiler yang baik. Perhatikan, contoh itu cenderung menjadi demonstrasi kecepatan yang buruk, tetapi dikombinasikan dengan penyelarasan + instruksi unit yang lebih besar, ini bisa lebih cepat daripada kebanyakan hal lain pada CPU tertentu.
Loop unrolling - branch bisa sangat mahal pada beberapa CPU, jadi membuka loop dapat menurunkan jumlah cabang. Ini juga merupakan teknik yang baik untuk menggabungkan instruksi SIMD dan unit berukuran sangat besar.
Misalnya, http://www.agner.org/optimize/#asmlib memiliki memcpypenerapan yang paling berhasil (dengan jumlah yang sangat kecil). Jika Anda membaca kode sumbernya, kode tersebut akan penuh dengan banyak kode perakitan sebaris yang menarik ketiga teknik di atas, memilih teknik mana dari teknik tersebut berdasarkan CPU yang Anda gunakan.
Perhatikan, ada juga pengoptimalan serupa yang dapat dilakukan untuk menemukan byte dalam buffer juga. strchr()dan teman-teman akan sering lebih cepat dari yang setara dengan lemparan tangan Anda. Ini terutama berlaku untuk .NET dan Java . Misalnya, dalam .NET, built-in String.IndexOf()jauh lebih cepat daripada pencarian string Boyer-Moore , karena menggunakan teknik pengoptimalan di atas.
Sebagian besar CPU saat ini memiliki prediksi cabang yang baik, yang seharusnya meniadakan manfaat loop unrolling dalam kasus-kasus tertentu. Kompiler pengoptimalan yang baik terkadang masih bisa menggunakannya.
thomasrutter
5
Jawaban singkat:
isi cache
wordsize mentransfer alih-alih yang byte jika memungkinkan
Perhatikan bahwa hal di atas bukan memcpykarena sengaja tidak menaikkan topenunjuk. Ini mengimplementasikan operasi yang sedikit berbeda: penulisan ke dalam register yang dipetakan memori. Lihat artikel Wikipedia untuk detailnya.
Perangkat Duff, atau hanya mekanisme lompatan awal, adalah penggunaan yang baik untuk menyalin 1..3 (atau 1..7) byte pertama sehingga penunjuk sejajar dengan batas yang lebih bagus di mana instruksi pemindahan memori yang lebih besar dapat digunakan.
Daemin
@ MarkByers: Kode menggambarkan operasi yang sedikit berbeda ( *tomengacu pada register yang dipetakan memori dan sengaja tidak bertambah - lihat artikel tertaut ke). Seperti yang saya pikir sudah saya jelaskan, jawaban saya tidak berusaha memberikan efisiensi memcpy, itu hanya menyebutkan teknik yang agak aneh.
NPE
@Daemin Setuju, seperti yang Anda katakan Anda bisa melewati do {} while () dan sakelar akan diterjemahkan ke tabel lompat oleh kompilator. Sangat berguna saat Anda ingin mengurus data yang tersisa. Peringatan harus disebutkan tentang perangkat Duff, tampaknya pada arsitektur yang lebih baru (x86 yang lebih baru), prediksi cabang sangat efisien sehingga perangkat Duff sebenarnya lebih lambat daripada loop sederhana.
onemasse
1
Oh tidak .. bukan perangkat Duff. Tolong jangan gunakan perangkat Duff. Silahkan. Gunakan PGO dan biarkan saya compiler melakukan loop unrolling untuk Anda di tempat yang masuk akal.
Billy ONeal
Tidak, perangkat Duff pasti tidak digunakan dalam implementasi modern apa pun.
gnasher729
3
Seperti orang lain mengatakan salinan memcpy lebih besar dari potongan 1-byte. Menyalin dalam potongan berukuran kata jauh lebih cepat. Namun, sebagian besar implementasi mengambil langkah lebih jauh dan menjalankan beberapa instruksi MOV (kata) sebelum melakukan perulangan. Keuntungan dari menyalin, katakanlah, 8 blok kata per loop adalah bahwa loop itu sendiri mahal. Teknik ini mengurangi jumlah cabang bersyarat dengan faktor 8, mengoptimalkan salinan untuk balok raksasa.
Saya rasa ini tidak benar. Anda dapat membatalkan gulungan, tetapi Anda tidak dapat menyalin dalam satu instruksi lebih banyak data daripada yang dapat dialamatkan pada satu waktu pada arsitektur target. Ditambah, ada overhead untuk membuka gulungannya juga ...
Billy ONeal
@ Billy ONeal: Menurutku bukan itu yang dimaksud VoidStar. Dengan memiliki beberapa instruksi gerakan berturut-turut, overhead penghitungan jumlah unit berkurang.
wallyk
@ Billy ONeal: Anda melewatkan intinya. 1-kata pada satu waktu adalah seperti MOV, JMP, MOV, JMP, dll. Dimana Anda dapat melakukan MOV MOV MOV MOV JMP. Saya telah menulis mempcy sebelumnya dan saya telah membandingkan banyak cara untuk melakukannya;)
VoidStar
@wallyk: Mungkin. Tapi dia mengatakan "menyalin potongan yang lebih besar" - yang sebenarnya tidak mungkin. Jika maksudnya loop unrolling, maka dia harus mengatakan "sebagian besar implementasi mengambil langkah lebih jauh dan membatalkan loop." Jawaban seperti yang tertulis paling banter menyesatkan, paling buruk salah.
Billy ONeal
@VoidStar: Setuju --- sekarang lebih baik. +1.
Billy ONeal
2
Jawaban yang besar, tetapi jika Anda masih ingin menerapkan cepat suatu memcpydiri Anda, ada sebuah posting blog menarik tentang memcpy cepat, memcpy Cepat di C .
Karena seperti banyak rutinitas perpustakaan, ini telah dioptimalkan untuk arsitektur yang Anda jalankan. Yang lain telah memposting berbagai teknik yang dapat digunakan.
Diberikan pilihan, gunakan rutinitas perpustakaan daripada roll Anda sendiri. Ini adalah variasi KERING yang saya sebut DRO (Don't Repeat Others). Selain itu, rutinitas perpustakaan cenderung tidak salah dibandingkan penerapan Anda sendiri.
Saya telah melihat pemeriksa akses memori mengeluh tentang pembacaan di luar batas pada memori atau buffer string yang bukan merupakan kelipatan dari ukuran kata. Ini adalah hasil dari pengoptimalan yang digunakan.
Anda dapat melihat implementasi MacOS dari memset, memcpy dan memmove.
Saat boot, OS menentukan prosesor mana yang menjalankannya. Ini telah membangun kode yang dioptimalkan secara khusus untuk setiap prosesor yang didukung, dan pada saat boot menyimpan instruksi jmp ke kode yang tepat di lokasi hanya baca / tetap.
Implementasi C memset, memcpy dan memmove hanyalah lompatan ke lokasi tetap itu.
Implementasi menggunakan kode yang berbeda tergantung pada penyelarasan sumber dan tujuan untuk memcpy dan memmove. Mereka jelas menggunakan semua kemampuan vektor yang tersedia. Mereka juga menggunakan varian non-caching saat Anda menyalin data dalam jumlah besar, dan memiliki instruksi untuk meminimalkan menunggu tabel halaman. Ini bukan hanya kode assembler, ini adalah kode assembler yang ditulis oleh seseorang dengan pengetahuan yang sangat baik tentang setiap arsitektur prosesor.
Intel juga menambahkan instruksi assembler yang dapat membuat operasi string lebih cepat. Misalnya dengan instruksi untuk mendukung strstr yang melakukan perbandingan 256 byte dalam satu siklus.
1
hinggaN
, selalu dari0
hinggaN-1
:-)int
sebagai penghitung ketika tipe unsigned likesize_t
harus digunakan sebagai gantinya.memcpy
ataumemmove
(tergantung pada apakah mereka dapat mengetahui apakah pointernya mungkin alias).Jawaban:
Karena memcpy menggunakan penunjuk kata daripada pengarah byte, implementasi memcpy juga sering ditulis dengan instruksi SIMD yang memungkinkan untuk mengacak 128 bit pada satu waktu.
Instruksi SIMD adalah instruksi perakitan yang dapat melakukan operasi yang sama pada setiap elemen dalam vektor hingga panjang 16 byte. Itu termasuk memuat dan menyimpan instruksi.
sumber
-O3
, itu akan menggunakan SIMD untuk loop, setidaknya jika ia tahupDest
danpSrc
tidak alias.Rutinitas penyalinan memori bisa jauh lebih rumit dan lebih cepat daripada penyalinan memori sederhana melalui petunjuk seperti:
Perbaikan
Perbaikan pertama yang dapat dilakukan seseorang adalah menyelaraskan salah satu petunjuk pada batas kata (menurut kata yang saya maksud adalah ukuran integer asli, biasanya 32 bit / 4 byte, tetapi bisa 64 bit / 8 byte pada arsitektur yang lebih baru) dan menggunakan gerakan ukuran kata / salin instruksi. Ini membutuhkan penggunaan salinan byte ke byte sampai pointer sejajar.
Arsitektur yang berbeda akan bekerja secara berbeda berdasarkan apakah sumber atau penunjuk tujuan disejajarkan dengan tepat. Misalnya pada prosesor XScale saya mendapatkan kinerja yang lebih baik dengan menyelaraskan penunjuk tujuan daripada penunjuk sumber.
Untuk lebih meningkatkan kinerja, beberapa loop unrolling dapat dilakukan, sehingga lebih banyak register prosesor yang dimuat dengan data dan itu berarti instruksi muat / penyimpanan dapat disisipkan dan latensinya disembunyikan oleh instruksi tambahan (seperti penghitungan loop, dll). Manfaat yang dibawa ini sedikit berbeda oleh prosesor, karena latensi instruksi muat / penyimpanan bisa sangat berbeda.
Pada tahap ini kode akhirnya ditulis dalam Assembly daripada C (atau C ++) karena Anda perlu menempatkan instruksi pemuatan dan penyimpanan secara manual untuk mendapatkan manfaat maksimal dari penyembunyian latensi dan throughput.
Umumnya, seluruh baris data cache harus disalin dalam satu iterasi dari loop yang tidak digulung.
Yang membawa saya ke peningkatan berikutnya, menambahkan pengambilan awal. Ini adalah instruksi khusus yang memberi tahu sistem cache prosesor untuk memuat bagian tertentu dari memori ke dalam cache-nya. Karena ada penundaan antara mengeluarkan instruksi dan mengisi baris cache, instruksi perlu ditempatkan sedemikian rupa sehingga data tersedia ketika akan disalin, dan tidak cepat / lambat.
Ini berarti meletakkan instruksi prefetch di awal fungsi serta di dalam loop salinan utama. Dengan instruksi prefetch di tengah-tengah loop salinan mengambil data yang akan disalin dalam beberapa waktu iterasi.
Saya tidak dapat mengingatnya, tetapi mungkin juga bermanfaat untuk mengambil lebih dulu alamat tujuan serta alamat sumber.
Faktor
Faktor utama yang mempengaruhi seberapa cepat memori dapat disalin adalah:
Jadi, jika Anda ingin menulis rutinitas mengatasi memori yang efisien dan cepat, Anda harus mengetahui cukup banyak tentang prosesor dan arsitektur yang Anda tulis. Cukuplah untuk mengatakan, kecuali Anda menulis pada beberapa platform tertanam, akan jauh lebih mudah untuk hanya menggunakan rutinitas penyalinan memori bawaan.
sumber
b_src & 0x3
tidak akan dikompilasi karena Anda tidak diizinkan melakukan aritmatika bitwise pada jenis penunjuk. Anda harus mentransmisikannya(u)intptr_t
terlebih dahulumemcpy
dapat menyalin lebih dari satu byte sekaligus tergantung pada arsitektur komputer. Kebanyakan komputer modern dapat bekerja dengan 32 bit atau lebih dalam satu instruksi prosesor.Dari satu contoh implementasi :
sumber
Anda dapat mengimplementasikan
memcpy()
menggunakan salah satu teknik berikut, beberapa bergantung pada arsitektur Anda untuk peningkatan performa, dan semuanya akan jauh lebih cepat daripada kode Anda:Gunakan unit yang lebih besar, seperti kata 32-bit, bukan byte. Anda juga dapat (atau mungkin harus) berurusan dengan penyelarasan di sini juga. Anda tidak dapat membaca / menulis kata 32-bit ke lokasi memori yang aneh misalnya di beberapa platform, dan di platform lain Anda membayar penalti performa yang sangat besar. Untuk mengatasinya, alamatnya harus berupa unit yang dapat dibagi 4. Anda dapat menggunakan hingga 64-bit untuk 64-bit CPU, atau bahkan lebih tinggi menggunakan instruksi SIMD (Instruksi tunggal, banyak data) ( MMX , SSE , dll.)
Anda dapat menggunakan instruksi CPU khusus yang kompilator Anda mungkin tidak dapat mengoptimalkan dari C. Misalnya, pada 80386, Anda dapat menggunakan instruksi awalan "rep" + instruksi "movsb" untuk memindahkan N byte yang ditentukan dengan menempatkan N dalam hitungan daftar. Kompiler yang baik hanya akan melakukan ini untuk Anda, tetapi Anda mungkin berada pada platform yang tidak memiliki kompiler yang baik. Perhatikan, contoh itu cenderung menjadi demonstrasi kecepatan yang buruk, tetapi dikombinasikan dengan penyelarasan + instruksi unit yang lebih besar, ini bisa lebih cepat daripada kebanyakan hal lain pada CPU tertentu.
Loop unrolling - branch bisa sangat mahal pada beberapa CPU, jadi membuka loop dapat menurunkan jumlah cabang. Ini juga merupakan teknik yang baik untuk menggabungkan instruksi SIMD dan unit berukuran sangat besar.
Misalnya, http://www.agner.org/optimize/#asmlib memiliki
memcpy
penerapan yang paling berhasil (dengan jumlah yang sangat kecil). Jika Anda membaca kode sumbernya, kode tersebut akan penuh dengan banyak kode perakitan sebaris yang menarik ketiga teknik di atas, memilih teknik mana dari teknik tersebut berdasarkan CPU yang Anda gunakan.Perhatikan, ada juga pengoptimalan serupa yang dapat dilakukan untuk menemukan byte dalam buffer juga.
strchr()
dan teman-teman akan sering lebih cepat dari yang setara dengan lemparan tangan Anda. Ini terutama berlaku untuk .NET dan Java . Misalnya, dalam .NET, built-inString.IndexOf()
jauh lebih cepat daripada pencarian string Boyer-Moore , karena menggunakan teknik pengoptimalan di atas.sumber
Jawaban singkat:
sumber
Saya tidak tahu apakah itu benar-benar digunakan dalam implementasi dunia nyata
memcpy
, tapi saya pikir Perangkat Duff layak disebutkan di sini.Dari Wikipedia :
Perhatikan bahwa hal di atas bukan
memcpy
karena sengaja tidak menaikkanto
penunjuk. Ini mengimplementasikan operasi yang sedikit berbeda: penulisan ke dalam register yang dipetakan memori. Lihat artikel Wikipedia untuk detailnya.sumber
*to
mengacu pada register yang dipetakan memori dan sengaja tidak bertambah - lihat artikel tertaut ke). Seperti yang saya pikir sudah saya jelaskan, jawaban saya tidak berusaha memberikan efisiensimemcpy
, itu hanya menyebutkan teknik yang agak aneh.Seperti orang lain mengatakan salinan memcpy lebih besar dari potongan 1-byte. Menyalin dalam potongan berukuran kata jauh lebih cepat. Namun, sebagian besar implementasi mengambil langkah lebih jauh dan menjalankan beberapa instruksi MOV (kata) sebelum melakukan perulangan. Keuntungan dari menyalin, katakanlah, 8 blok kata per loop adalah bahwa loop itu sendiri mahal. Teknik ini mengurangi jumlah cabang bersyarat dengan faktor 8, mengoptimalkan salinan untuk balok raksasa.
sumber
Jawaban yang besar, tetapi jika Anda masih ingin menerapkan cepat suatu
memcpy
diri Anda, ada sebuah posting blog menarik tentang memcpy cepat, memcpy Cepat di C .Bahkan, bisa lebih baik lagi dengan mengoptimalkan akses memori.
sumber
Karena seperti banyak rutinitas perpustakaan, ini telah dioptimalkan untuk arsitektur yang Anda jalankan. Yang lain telah memposting berbagai teknik yang dapat digunakan.
Diberikan pilihan, gunakan rutinitas perpustakaan daripada roll Anda sendiri. Ini adalah variasi KERING yang saya sebut DRO (Don't Repeat Others). Selain itu, rutinitas perpustakaan cenderung tidak salah dibandingkan penerapan Anda sendiri.
Saya telah melihat pemeriksa akses memori mengeluh tentang pembacaan di luar batas pada memori atau buffer string yang bukan merupakan kelipatan dari ukuran kata. Ini adalah hasil dari pengoptimalan yang digunakan.
sumber
Anda dapat melihat implementasi MacOS dari memset, memcpy dan memmove.
Saat boot, OS menentukan prosesor mana yang menjalankannya. Ini telah membangun kode yang dioptimalkan secara khusus untuk setiap prosesor yang didukung, dan pada saat boot menyimpan instruksi jmp ke kode yang tepat di lokasi hanya baca / tetap.
Implementasi C memset, memcpy dan memmove hanyalah lompatan ke lokasi tetap itu.
Implementasi menggunakan kode yang berbeda tergantung pada penyelarasan sumber dan tujuan untuk memcpy dan memmove. Mereka jelas menggunakan semua kemampuan vektor yang tersedia. Mereka juga menggunakan varian non-caching saat Anda menyalin data dalam jumlah besar, dan memiliki instruksi untuk meminimalkan menunggu tabel halaman. Ini bukan hanya kode assembler, ini adalah kode assembler yang ditulis oleh seseorang dengan pengetahuan yang sangat baik tentang setiap arsitektur prosesor.
Intel juga menambahkan instruksi assembler yang dapat membuat operasi string lebih cepat. Misalnya dengan instruksi untuk mendukung strstr yang melakukan perbandingan 256 byte dalam satu siklus.
sumber