Saya mendengar beberapa orang mengungkapkan kekhawatiran tentang operator "+" di std :: string dan berbagai solusi untuk mempercepat penggabungan. Apakah semua ini benar-benar diperlukan? Jika ya, apa cara terbaik untuk menggabungkan string di C ++?
108
libstdc++
melakukan ini, misalnya . Jadi, ketika memanggil operator + dengan temporaries, itu dapat mencapai kinerja yang hampir sama baiknya - mungkin sebuah argumen yang mendukung default, demi keterbacaan, kecuali jika seseorang memiliki tolok ukur yang menunjukkan bahwa itu adalah hambatan. Namun, variadic Standarappend()
akan menjadi optimal dan dapat dibaca ...Jawaban:
Pekerjaan ekstra mungkin tidak sepadan, kecuali jika Anda benar-benar membutuhkan efisiensi. Anda mungkin akan memiliki efisiensi yang jauh lebih baik hanya dengan menggunakan operator + = sebagai gantinya.
Sekarang setelah pelepasan tanggung jawab hukum itu, saya akan menjawab pertanyaan Anda yang sebenarnya ...
Efisiensi kelas string STL bergantung pada implementasi STL yang Anda gunakan.
Anda dapat menjamin efisiensi dan memiliki kendali yang lebih besar dengan melakukan penggabungan secara manual melalui c fungsi bawaan.
Mengapa operator + tidak efisien:
Lihat antarmuka ini:
Anda dapat melihat bahwa objek baru dikembalikan setelah setiap tanda +. Itu berarti buffer baru digunakan setiap saat. Jika Anda melakukan banyak + operasi tambahan, itu tidak efisien.
Mengapa Anda bisa membuatnya lebih efisien:
Pertimbangan untuk implementasi:
Struktur data tali:
Jika Anda membutuhkan penggabungan yang sangat cepat, pertimbangkan untuk menggunakan struktur data tali .
sumber
Pesan ruang terakhir Anda sebelumnya, lalu gunakan metode append dengan buffer. Misalnya, Anda mengharapkan panjang string akhir Anda menjadi 1 juta karakter:
sumber
Aku tidak akan khawatir tentang hal itu. Jika Anda melakukannya dalam satu loop, string akan selalu mengalokasikan memori untuk meminimalkan realokasi - cukup gunakan
operator+=
dalam kasus itu. Dan jika Anda melakukannya secara manual, seperti ini atau lebih lamaKemudian itu membuat temporaries - bahkan jika kompilator bisa menghilangkan beberapa salinan nilai yang dikembalikan. Itu karena dalam pemanggilan berturut-turut
operator+
tidak mengetahui apakah parameter referensi mereferensikan objek bernama atau sementara dikembalikan dari suboperator+
pemanggilan. Saya lebih suka tidak khawatir tentang itu sebelum tidak membuat profil terlebih dahulu. Tapi mari kita ambil contoh untuk menunjukkannya. Kami pertama kali memperkenalkan tanda kurung untuk memperjelas pengikatan. Saya meletakkan argumen langsung setelah deklarasi fungsi yang digunakan untuk kejelasan. Di bawah itu, saya tunjukkan apa ekspresi yang dihasilkan kemudian:Sekarang, sebagai tambahan,
tmp1
adalah apa yang dikembalikan oleh panggilan pertama ke operator + dengan argumen yang ditampilkan. Kami menganggap kompilator benar-benar pintar dan mengoptimalkan salinan nilai kembalian. Jadi kita berakhir dengan satu string baru yang berisi penggabungan daria
dan" : "
. Sekarang, ini terjadi:Bandingkan dengan yang berikut ini:
Ini menggunakan fungsi yang sama untuk sementara dan untuk string bernama! Jadi kompilator harus menyalin argumen ke string baru dan menambahkannya dan mengembalikannya dari badan
operator+
. Itu tidak bisa mengambil memori sementara dan menambahkannya. Semakin besar ekspresi, semakin banyak salinan string yang harus dilakukan.Visual Studio dan GCC berikutnya akan mendukung semantik pemindahan c ++ 1x (melengkapi semantik salinan ) dan rvalue referensi sebagai tambahan eksperimental. Itu memungkinkan untuk mengetahui apakah parameter mereferensikan sementara atau tidak. Ini akan membuat penambahan seperti itu sangat cepat, karena semua hal di atas akan berakhir dalam satu "pipa tambahan" tanpa salinan.
Jika ternyata menjadi hambatan, Anda tetap bisa melakukannya
The
append
panggilan menambahkan argumen untuk*this
dan kemudian kembali referensi untuk diri mereka sendiri. Jadi tidak ada penyalinan sementara yang dilakukan di sana. Atau sebagai alternatif,operator+=
dapat digunakan, tetapi Anda akan membutuhkan tanda kurung yang jelek untuk memperbaiki prioritas.sumber
libstdc++
untukoperator+(string const& lhs, string&& rhs)
melakukanreturn std::move(rhs.insert(0, lhs))
. Kemudian jika keduanya adalah temporer,operator+(string&& lhs, string&& rhs)
jikalhs
memiliki kapasitas yang memadai akan tersedia secara langsungappend()
. Di mana menurut saya risiko ini menjadi lebih lambat daripadaoperator+=
jikalhs
tidak memiliki kapasitas yang cukup, karena kemudian jatuh kembali kerhs.insert(0, lhs)
, yang tidak hanya harus memperluas buffer & menambahkan konten baru sepertiappend()
, tetapi juga perlu menggeser konten asli sesuai keinginanrhs
.operator+=
adalah yangoperator+
masih harus mengembalikan nilai, jadi harus kemove()
operan mana yang ditambahkan. Namun, saya rasa itu adalah overhead yang cukup kecil (menyalin beberapa petunjuk / ukuran) dibandingkan dengan menyalin seluruh string, jadi itu bagus!Untuk sebagian besar aplikasi, itu tidak masalah. Cukup tulis kode Anda, tanpa menyadari bagaimana tepatnya operator + bekerja, dan hanya menangani masalah dengan tangan Anda sendiri jika itu menjadi hambatan yang nyata.
sumber
Tidak seperti .NET System.Strings, std :: strings C ++ dapat berubah, dan oleh karena itu dapat dibangun melalui penggabungan sederhana secepat melalui metode lainnya.
sumber
operator+
tidak harus mengembalikan string baru. Pelaksana dapat mengembalikan salah satu operannya, dimodifikasi, jika operan itu diteruskan oleh referensi nilai r.libstdc++
melakukan ini, misalnya . Jadi, saat memanggiloperator+
dengan temporaries, itu dapat mencapai kinerja yang sama atau hampir sama baiknya - yang mungkin menjadi argumen lain yang mendukung default untuk itu kecuali jika seseorang memiliki tolok ukur yang menunjukkan bahwa itu mewakili kemacetan.mungkin std :: stringstream sebagai gantinya?
Tetapi saya setuju dengan sentimen bahwa Anda mungkin harus membuatnya tetap dapat dipelihara dan dimengerti dan kemudian profil untuk melihat apakah Anda benar-benar mengalami masalah.
sumber
Dalam Imperfect C ++ , Matthew Wilson menyajikan penggabung string dinamis yang menghitung sebelumnya panjang string akhir agar hanya memiliki satu alokasi sebelum menggabungkan semua bagian. Kita juga bisa mengimplementasikan concatenator statis dengan bermain dengan template ekspresi .
Ide semacam itu telah diimplementasikan dalam implementasi STLport std :: string - yang tidak sesuai dengan standar karena peretasan yang tepat ini.
sumber
Glib::ustring::compose()
dari ikatan glibmm ke GLib melakukan itu: memperkirakan dan mengukurreserve()
panjang akhir berdasarkan format string yang disediakan dan vararg, laluappend()
masing-masing (atau penggantinya yang diformat) dalam satu lingkaran. Saya berharap ini adalah cara kerja yang cukup umum.std::string
operator+
mengalokasikan string baru dan menyalin dua string operan setiap saat. ulangi berkali-kali dan itu menjadi mahal, O (n).std::string
append
danoperator+=
di sisi lain, tingkatkan kapasitas sebesar 50% setiap kali tali perlu tumbuh. Yang mengurangi jumlah alokasi memori dan operasi penyalinan secara signifikan, O (log n).sumber
operator+
mana satu atau kedua argumen dilewatkan oleh referensi rvalue dapat menghindari alokasi string baru sama sekali dengan menggabungkan ke buffer yang ada dari salah satu operand (meskipun mungkin harus dialokasikan kembali jika kapasitasnya tidak mencukupi).Untuk string kecil tidak masalah. Jika Anda memiliki string besar, sebaiknya Anda menyimpannya dalam bentuk vektor atau di koleksi lain sebagai bagian. Dan tambahkan algoritme Anda untuk bekerja dengan kumpulan data seperti itu, bukan dengan satu string besar.
Saya lebih suka std :: ostringstream untuk penggabungan kompleks.
sumber
Seperti kebanyakan hal, lebih mudah untuk tidak melakukan sesuatu daripada melakukannya.
Jika Anda ingin mengeluarkan string besar ke GUI, mungkin apa pun yang Anda hasilkan dapat menangani string dalam potongan lebih baik daripada sebagai string besar (misalnya, menggabungkan teks dalam editor teks - biasanya mereka membuat baris terpisah struktur).
Jika Anda ingin mengeluarkan ke file, streaming data daripada membuat string besar dan mengeluarkannya.
Saya tidak pernah menemukan kebutuhan untuk membuat penggabungan lebih cepat diperlukan jika saya menghapus penggabungan yang tidak perlu dari kode lambat.
sumber
Mungkin performa terbaik jika Anda mengalokasikan (memesan) ruang sebelumnya dalam string yang dihasilkan.
Pemakaian:
sumber
Larik karakter sederhana, yang dikemas dalam kelas yang melacak ukuran larik dan jumlah byte yang dialokasikan adalah yang tercepat.
Triknya adalah dengan melakukan satu alokasi besar di awal.
di
https://github.com/pedro-vicente/table-string
Tolak ukur
Untuk Visual Studio 2015, x86 debug build, peningkatan substansial melalui C ++ std :: string.
sumber
std::string
. Mereka tidak meminta kelas string alternatif.Anda dapat mencoba yang ini dengan reservasi memori untuk setiap item:
sumber