Apakah ada padanan non-atomik dari std :: shared_ptr? Dan mengapa tidak ada di <memory>?

90

Ini sedikit dari dua pertanyaan bagian, semua tentang atomisitas std::shared_ptr:

1. Sejauh yang saya tahu, std::shared_ptradalah satu-satunya penunjuk cerdas dalam <memory>atom itu. Saya bertanya-tanya apakah ada versi non-atomik yang std::shared_ptrtersedia (saya tidak dapat melihat apa pun di dalamnya <memory>, jadi saya juga terbuka untuk saran di luar standar, seperti yang ada di Boost). Saya tahu boost::shared_ptrjuga atom (jika BOOST_SP_DISABLE_THREADStidak ditentukan), tetapi mungkin ada alternatif lain? Saya mencari sesuatu yang memiliki semantik yang sama std::shared_ptr, tetapi tanpa atomicity.

2. Saya mengerti mengapa std::shared_ptratom; itu bagus. Namun, itu tidak baik untuk setiap situasi, dan C ++ secara historis memiliki mantra "hanya bayar untuk apa yang Anda gunakan." Jika saya tidak menggunakan banyak utas, atau jika saya menggunakan beberapa utas tetapi tidak berbagi kepemilikan penunjuk di utas, penunjuk cerdas atom berlebihan. Pertanyaan kedua saya adalah mengapa tidak ada versi non-atomik yang std::shared_ptrdisediakan dalam C ++ 11 ? (dengan asumsi ada alasannya ) (jika jawabannya hanya "versi non-atom tidak pernah dipertimbangkan" atau "tidak ada yang pernah meminta versi non-atom" itu bagus!).

Dengan pertanyaan # 2, saya bertanya-tanya apakah seseorang pernah mengusulkan versi non-atomik shared_ptr(baik untuk Boost atau komite standar) (bukan untuk menggantikan versi atom shared_ptr, tetapi untuk hidup berdampingan dengannya) dan itu ditolak untuk a alasan spesifik.

Batang jagung
sumber
4
Apa sebenarnya "biaya" yang Anda khawatirkan di sini? Biaya untuk menambah bilangan bulat secara atom? Apakah itu sebenarnya biaya yang menjadi perhatian Anda untuk aplikasi nyata? Atau apakah Anda hanya mengoptimalkan sebelum waktunya?
Nicol Bolas
9
@NicolBolas: Ini lebih keingintahuan daripada apa pun; Saya (saat ini) tidak memiliki kode / proyek di mana saya benar-benar ingin menggunakan penunjuk bersama non-atomik. Namun, saya memiliki proyek (di masa lalu) di mana Boost shared_ptrmengalami perlambatan yang signifikan karena atomicity, dan mendefinisikan BOOST_DISABLE_THREADSmembuat perbedaan yang nyata (saya tidak tahu apakah std::shared_ptrakan memiliki biaya yang sama dengan yang boost::shared_ptrterjadi).
Cornstalks
13
@ Pemilih tertutup: bagian pertanyaan mana yang tidak konstruktif? Jika tidak ada alasan spesifik untuk pertanyaan kedua, tidak apa-apa (jawaban sederhana "itu tidak dipertimbangkan" akan menjadi jawaban yang cukup valid). Saya ingin tahu apakah ada alasan / alasan tertentu yang ada. Dan pertanyaan pertama tentu saja adalah pertanyaan yang valid, menurut saya. Jika saya perlu mengklarifikasi pertanyaan, atau membuat sedikit penyesuaian, beri tahu saya. Tapi saya tidak melihat bagaimana itu tidak konstruktif.
Cornstalks
10
@ Cornstalks Yah, mungkin saja orang-orang tidak bereaksi dengan baik pada pertanyaan yang dapat dengan mudah mereka anggap sebagai "pengoptimalan prematur" , tidak peduli seberapa valid, posisinya, atau relevan pertanyaannya, saya rasa. Saya sendiri tidak melihat alasan untuk menutup ini sebagai hal yang tidak konstruktif.
Christian Rau
13
(tidak bisa menulis jawaban sekarang sudah ditutup, jadi berkomentar) Dengan GCC ketika program Anda tidak menggunakan banyak utas shared_ptrtidak menggunakan operasi atom untuk refcount. Lihat (2) di gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html untuk patch ke GCC untuk memungkinkan implementasi non-atomik digunakan bahkan di aplikasi multithread, untuk shared_ptrobjek yang tidak dibagi antara benang. Saya telah duduk di tambalan itu selama bertahun-tahun tetapi saya akhirnya mempertimbangkan untuk melakukannya untuk GCC 4.9
Jonathan Wakely

Jawaban:

107

1. Saya ingin tahu apakah ada versi non-atomic dari std :: shared_ptr yang tersedia

Tidak disediakan oleh standar. Mungkin ada satu yang disediakan oleh perpustakaan "pihak ketiga". Memang, sebelum C ++ 11, dan sebelum Boost, sepertinya semua orang menulis referensi mereka sendiri yang dihitung oleh penunjuk cerdas (termasuk saya).

2. Pertanyaan kedua saya adalah mengapa versi non-atomic dari std :: shared_ptr tersedia di C ++ 11?

Pertanyaan ini dibahas pada pertemuan Rapperswil tahun 2010. Hal tersebut dikemukakan oleh National Body Comment # 20 oleh Swiss. Ada argumen kuat di kedua sisi perdebatan, termasuk yang Anda berikan dalam pertanyaan Anda. Namun, di akhir diskusi, pemungutan suara sangat (tetapi tidak bulat) menentang penambahan versi yang tidak sinkron (non-atomik) dari shared_ptr.

Argumen yang menentang termasuk:

  • Kode yang ditulis dengan shared_ptr yang tidak disinkronkan mungkin akan digunakan dalam kode berulir di masa mendatang, yang akhirnya menyebabkan kesulitan untuk men-debug masalah tanpa peringatan.

  • Memiliki satu "universal" shared_ptr yang merupakan "satu cara" untuk memproses penghitungan referensi memiliki keuntungan: Dari proposal asli :

    Memiliki tipe objek yang sama terlepas dari fitur yang digunakan, sangat memfasilitasi interoperabilitas antar pustaka, termasuk pustaka pihak ketiga.

  • Biaya atom, meskipun tidak nol, tidak terlalu besar. Biaya dikurangi dengan penggunaan konstruksi bergerak dan tugas pindah yang tidak perlu menggunakan operasi atom. Operasi semacam itu biasanya digunakan dalam vector<shared_ptr<T>>menghapus dan menyisipkan.

  • Tidak ada yang melarang orang untuk menulis penunjuk cerdas yang dihitung referensi non-atomik mereka sendiri jika itu benar-benar yang ingin mereka lakukan.

Kata terakhir dari POKJA di Rapperswil hari itu adalah:

Tolak CH 20. Tidak ada konsensus untuk membuat perubahan saat ini.

Howard Hinnant
sumber
7
Wow, sempurna, terima kasih atas informasinya! Itulah jenis informasi yang ingin saya temukan.
Cornstalks
> Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries. itu alasan yang sangat aneh. Library pihak ketiga akan menyediakan tipenya sendiri, jadi mengapa penting jika mereka menyediakannya dalam bentuk std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType>, dll? Anda harus selalu menyesuaikan kode Anda dengan apa yang dikembalikan perpustakaan
Jean-Michaël Celerier
Itu benar untuk jenis khusus perpustakaan, tetapi idenya adalah bahwa ada juga banyak tempat di mana jenis standar muncul di API pihak ketiga. Misalnya, perpustakaan saya mungkin ada std::shared_ptr<std::string>di suatu tempat. Jika perpustakaan orang lain menggunakan jenis itu juga, penelepon dapat memberikan string yang sama kepada kami berdua tanpa ketidaknyamanan atau overhead untuk mengonversi antara representasi yang berbeda, dan itu adalah kemenangan kecil bagi semua orang.
Jack O'Connor
52

Howard sudah menjawab pertanyaan itu dengan baik, dan Nicol membuat beberapa poin bagus tentang manfaat memiliki satu tipe penunjuk bersama standar, daripada banyak yang tidak kompatibel.

Sementara saya sepenuhnya setuju dengan keputusan komite, saya pikir ada beberapa manfaat untuk menggunakan unsynchronized shared_ptrjenis -seperti dalam kasus khusus , jadi saya sudah menyelidiki topik beberapa kali.

Jika saya tidak menggunakan beberapa utas, atau jika saya menggunakan banyak utas tetapi tidak berbagi kepemilikan penunjuk di utas, penunjuk cerdas atom berlebihan.

Dengan GCC, saat program Anda tidak menggunakan beberapa utas, shared_ptr tidak menggunakan operasi atomik untuk refcount tersebut. Ini dilakukan dengan memperbarui jumlah referensi melalui fungsi pembungkus yang mendeteksi apakah program multithread (pada GNU / Linux ini dilakukan hanya dengan mendeteksi apakah program terhubung ke libpthread.so) dan mengirimkannya ke operasi atom atau non-atomik sesuai.

Saya menyadari bertahun-tahun yang lalu bahwa karena GCC shared_ptr<T>diimplementasikan dalam hal __shared_ptr<T, _LockPolicy>kelas dasar , dimungkinkan untuk menggunakan kelas dasar dengan kebijakan penguncian utas tunggal bahkan dalam kode multithread, dengan menggunakan secara eksplisit __shared_ptr<T, __gnu_cxx::_S_single>. Sayangnya, karena itu bukan kasus penggunaan yang dimaksudkan, ia tidak bekerja secara optimal sebelum GCC 4.9, dan beberapa operasi masih menggunakan fungsi pembungkus sehingga dikirim ke operasi atomik meskipun Anda secara eksplisit meminta _S_singlekebijakan tersebut. Lihat poin (2) di http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.htmluntuk mengetahui detail selengkapnya dan patch ke GCC untuk memungkinkan implementasi non-atomik digunakan bahkan dalam aplikasi multithread. Saya duduk di tambalan itu selama bertahun-tahun tetapi akhirnya saya berkomitmen untuk GCC 4.9, yang memungkinkan Anda menggunakan templat alias seperti ini untuk menentukan jenis penunjuk bersama yang tidak aman untuk utas, tetapi sedikit lebih cepat:

template<typename T>
  using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;

Jenis ini tidak akan dapat dioperasikan dengan std::shared_ptr<T>dan hanya akan aman digunakan jika ada jaminan bahwa shared_ptr_unsynchronizedobjek tidak akan pernah dibagikan di antara utas tanpa sinkronisasi tambahan yang disediakan pengguna.

Ini tentu saja sepenuhnya non-portabel, tetapi terkadang tidak apa-apa. Dengan peretasan preprocessor yang tepat, kode Anda masih akan berfungsi dengan baik dengan implementasi lain jika shared_ptr_unsynchronized<T>merupakan alias shared_ptr<T>, hanya akan sedikit lebih cepat dengan GCC.


Jika Anda menggunakan GCC sebelum versi 4.9, Anda dapat menggunakannya dengan menambahkan _Sp_counted_base<_S_single>spesialisasi eksplisit ke kode Anda sendiri (dan memastikan tidak ada yang membuat instance __shared_ptr<T, _S_single>tanpa menyertakan spesialisasi, untuk menghindari pelanggaran ODR.) Menambahkan spesialisasi stdjenis semacam itu secara teknis tidak ditentukan, tetapi akan berfungsi dalam praktik, karena dalam hal ini tidak ada perbedaan antara saya menambahkan spesialisasi ke GCC atau Anda menambahkannya ke kode Anda sendiri.

Jonathan Wakely
sumber
2
Hanya ingin tahu, apakah ada kesalahan ketik dalam contoh alias template Anda? Ie saya pikir itu harus membaca shared_ptr_unsynchronized = std :: __ shared_ptr <. Kebetulan, saya menguji ini hari ini, sehubungan dengan std :: __ enable_shared_from_this dan std :: __ weak_ptr, dan tampaknya berfungsi dengan baik (gcc 4.9 dan gcc 5.2). Saya akan membuat profil / membongkar segera untuk melihat apakah memang operasi atom dilewati.
Carl Cook
Detail yang luar biasa! Baru-baru ini saya menghadapi masalah, seperti yang dijelaskan dalam pertanyaan ini , yang akhirnya membuat saya untuk melihat ke dalam kode sumber std::shared_ptr, std::__shared_ptr, __default_lock_policydan semacamnya. Jawaban ini menegaskan apa yang saya pahami dari kode itu.
Nawaz
21

Pertanyaan kedua saya adalah mengapa versi non-atomic dari std :: shared_ptr tersedia di C ++ 11? (dengan asumsi ada alasannya).

Seseorang dapat dengan mudah bertanya mengapa tidak ada penunjuk yang mengganggu, atau sejumlah kemungkinan variasi lain dari penunjuk bersama yang dapat dimiliki.

Desain shared_ptr, yang diturunkan dari Boost, telah menciptakan bahasa standar minimum untuk penunjuk pintar. Itu, secara umum, Anda bisa menarik ini dari dinding dan menggunakannya. Itu adalah sesuatu yang akan digunakan secara umum, di berbagai aplikasi. Anda dapat meletakkannya di antarmuka, dan kemungkinan besar orang baik akan mau menggunakannya.

Threading hanya akan menjadi lebih umum di masa mendatang. Memang, seiring berjalannya waktu, threading pada umumnya akan menjadi salah satu cara utama untuk mencapai kinerja. Mewajibkan penunjuk cerdas dasar untuk melakukan hal minimum yang diperlukan untuk mendukung penguliran memfasilitasi kenyataan ini.

Membuang setengah lusin pointer pintar dengan variasi kecil di antara mereka ke dalam standar, atau bahkan lebih buruk lagi, pointer pintar berbasis kebijakan, akan sangat buruk. Setiap orang akan memilih penunjuk yang paling mereka sukai dan mengabaikan semua yang lain. Tidak ada yang bisa berkomunikasi dengan orang lain. Ini akan seperti situasi saat ini dengan string C ++, di mana setiap orang memiliki tipenya sendiri. Hanya saja jauh lebih buruk, karena interoperation dengan string jauh lebih mudah daripada interoperation antara kelas-kelas pointer pintar.

Boost, dan panitia, memilih smart pointer tertentu untuk digunakan. Ini memberikan keseimbangan fitur yang baik dan secara luas dan umum digunakan dalam praktik.

std::vectormemiliki beberapa inefisiensi dibandingkan dengan array telanjang dalam beberapa kasus sudut juga. Ini memiliki beberapa batasan; beberapa penggunaan benar-benar ingin memiliki batasan keras pada ukuran a vector, tanpa menggunakan pengalokasi lemparan. Namun, panitia tidak mendesain vectormenjadi segalanya untuk semua orang. Ini dirancang untuk menjadi default yang baik untuk sebagian besar aplikasi. Mereka yang tidak dapat melakukannya dapat menulis alternatif yang sesuai dengan kebutuhan mereka.

Seperti yang Anda bisa untuk penunjuk cerdas jika shared_ptratomicity adalah beban. Kemudian lagi, seseorang mungkin juga mempertimbangkan untuk tidak terlalu sering menyalinnya.

Nicol Bolas
sumber
7
+1 untuk "seseorang mungkin juga mempertimbangkan untuk tidak terlalu sering menyalinnya".
Ali
Jika Anda pernah menghubungkan seorang profiler, Anda istimewa dan Anda bisa mengabaikan argumen seperti di atas. Jika Anda tidak memiliki persyaratan operasional yang sulit dipenuhi, Anda tidak boleh menggunakan C ++. Berdebat seperti yang Anda lakukan adalah cara yang baik untuk membuat C ++ secara universal dicerca oleh siapa pun yang tertarik dengan kinerja tinggi atau latensi rendah. Inilah mengapa pemrogram game tidak menggunakan STL, boost, atau bahkan pengecualian.
Hans Malherbe
Untuk kejelasan, saya pikir kutipan di bagian atas jawaban Anda harus membaca "mengapa versi non-atomic dari std :: shared_ptr disediakan dalam C ++ 11?"
Charles Savoie
4

Saya sedang mempersiapkan pembicaraan tentang shared_ptr di tempat kerja. Saya telah menggunakan boost shared_ptr yang dimodifikasi dengan menghindari malloc terpisah (seperti yang dapat dilakukan make_shared) dan parameter template untuk kebijakan kunci seperti shared_ptr_unsynchronized yang disebutkan di atas. Saya menggunakan program dari

http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html

sebagai ujian, setelah membersihkan salinan shared_ptr yang tidak perlu. Program hanya menggunakan utas utama dan argumen uji ditampilkan. Tes env adalah notebook yang menjalankan linuxmint 14. Berikut waktu yang dibutuhkan dalam hitungan detik:

uji coba setup boost (1.49) std dengan make_shared modifikasi boost
mt-unsafe (11) 11.9 9 / 11.5 (-pasal pada) 8.4  
atom (11) 13.6 12.4 13.0  
mt-unsafe (12) 113.5 85.8 / 108.9 (-read on) 81.5  
atom (12) 126.0 109.1 123.6  

Hanya versi 'std' yang menggunakan -std = cxx11, dan -pthread kemungkinan akan mengganti lock_policy di kelas g ++ __shared_ptr.

Dari angka-angka ini, saya melihat dampak instruksi atom pada pengoptimalan kode. Kasus uji tidak menggunakan wadah C ++ apa pun, tetapi vector<shared_ptr<some_small_POD>>kemungkinan akan rusak jika objek tidak memerlukan perlindungan utas. Boost tidak terlalu menderita karena malloc tambahan membatasi jumlah optimisasi kode dan inline.

Saya belum menemukan mesin dengan core yang cukup untuk menguji skalabilitas instruksi atom, tetapi menggunakan std :: shared_ptr hanya jika diperlukan mungkin lebih baik.

russ
sumber
4

Boost memberikan shared_ptryang non-atom. Ini disebut local_shared_ptr, dan dapat ditemukan di pustaka penunjuk cerdas peningkatan.

Fisikawan Kuantum
sumber
+1 untuk balasan singkat yang solid dengan kutipan yang bagus, tetapi tipe penunjuk ini terlihat mahal - dalam hal memori dan runtime, karena satu tingkat tipuan ekstra (local-> shared-> ptr vs shared-> ptr).
Red. Gelombang
@ Red.Wave Bisakah Anda menjelaskan apa yang Anda maksud dengan tipu muslihat dan bagaimana hal itu memengaruhi kinerja? Apakah maksud Anda itu adalah shared_ptrdengan konter, meskipun itu lokal? Atau maksud Anda ada masalah lain dengan itu? Para dokter mengatakan bahwa satu - satunya perbedaan adalah bahwa ini bukan atom.
Fisikawan Kuantum
Setiap ptr lokal menyimpan hitungan dan referensi ke ptr bersama yang asli. Jadi setiap akses ke pointee terakhir membutuhkan derefrence dari lokal ke ptr bersama, yang kemudian derefrence ke pointee. Jadi ada satu lagi tipuan yang ditumpuk ke arah tipuan dari ptr bersama. Dan itu meningkatkan biaya overhead.
Red. Gelombang
@ Red.Wave Dari mana Anda mendapatkan informasi ini? Ini: "Jadi, setiap akses ke poin akhir memerlukan perbedaan dari lokal ke ptr bersama" membutuhkan beberapa kutipan. Saya tidak bisa menemukannya di dokumen boost. Sekali lagi, apa yang saya lihat di dokumen adalah bahwa ia mengatakan itu local_shared_ptrdan shared_ptridentik kecuali untuk atom. Saya benar-benar tertarik untuk mengetahui apakah yang Anda katakan itu benar karena saya gunakan local_shared_ptrdalam aplikasi yang membutuhkan kinerja tinggi.
Fisikawan Kuantum
3
@ Red.Wave Jika Anda melihat kode sumber sebenarnya github.com/boostorg/smart_ptr/blob/ .. Anda akan melihat bahwa tidak ada tipuan ganda. Paragraf dalam dokumentasi ini hanyalah model mental.
Ilya Popov