Std :: shared_ptr'nin atomik olmayan bir eşdeğeri var mı? Ve neden <memory> 'de bir tane yok?


90

Bu biraz iki bölümden oluşan bir sorudur, tümü aşağıdakilerin atomikliği ile ilgilidir std::shared_ptr:

1. Söyleyebileceğim kadarıyla atomik std::shared_ptrolan tek akıllı işaretçi <memory>. std::shared_ptrKullanılabilirin atomik olmayan bir sürümü olup olmadığını merak ediyorum (içinde hiçbir şey göremiyorum <memory>, bu yüzden Boost'takiler gibi standart dışındaki önerilere de açığım). boost::shared_ptrAtomik olduğunu da biliyorum ( BOOST_SP_DISABLE_THREADStanımlanmamışsa), ama belki başka bir alternatif var mı? std::shared_ptrAtomiklik içermeyen ama aynı semantiğe sahip bir şey arıyorum .

2. Neden std::shared_ptratomik olduğunu anlıyorum ; bu çok hoş. Ancak, bu her durum için hoş değildir ve C ++ tarihsel olarak "yalnızca kullandığınız kadar ödeyin" mantrasına sahip olmuştur. Birden fazla iş parçacığı kullanmıyorsam veya birden çok iş parçacığı kullanıyorsam ancak işaretçi sahipliğini iş parçacıkları arasında paylaşmıyorsam, atomik bir akıllı işaretçi aşırı derecede önemlidir. İkinci sorum, C ++ 11'de neden atomik olmayan bir sürüm std::shared_ptrsağlanmadı ? (bir neden olduğunu varsayarak ) (cevap basitçe "atomik olmayan bir versiyon asla düşünülmedi" veya "hiç kimse atomik olmayan bir versiyon istemedi" ise sorun değil!).

2 numaralı soruyla, birinin atomik olmayan bir versiyonunu önerip shared_ptr(Boost'a veya standartlar komitesine) (atomik versiyonunun yerini almak için değil shared_ptr, onunla birlikte var olmak için) önerip önermediğini merak ediyorum. özel sebep.


4
Burada tam olarak ne "maliyet" ile ilgileniyorsunuz? Bir tamsayıyı atomik olarak artırmanın maliyeti? Bu gerçekten herhangi bir gerçek uygulama için sizi endişelendiren bir maliyet mi? Yoksa erken mi optimize ediyorsun?
Nicol Bolas

9
@NicolBolas: Her şeyden çok merak; (Şu anda) atomik olmayan bir paylaşımlı işaretçi kullanmak istediğim herhangi bir kodum / projem yok. Bununla birlikte, shared_ptrBoost'un atomikliği nedeniyle önemli bir yavaşlama olduğu ve tanımlamanın BOOST_DISABLE_THREADSgözle görülür bir fark yarattığı (geçmişte) projelerim vardı ( bununla std::shared_ptraynı maliyete sahip olup olmayacağını bilmiyorum boost::shared_ptr).
Cornstalks

13
@Yakın seçmenler: Sorunun hangi kısmı yapıcı değil? İkinci soru için belirli bir neden yoksa sorun değil (basit bir "basitçe dikkate alınmadı" yeterince geçerli bir cevap olacaktır). Var olan belirli bir neden / mantık olup olmadığını merak ediyorum . Ve ilk sorunun kesinlikle geçerli bir soru olduğunu söyleyebilirim. Soruyu netleştirmem veya üzerinde küçük ayarlamalar yapmam gerekirse lütfen bana bildirin. Ama ne kadar yapıcı olmadığını anlamıyorum.
Mısır sapı

10
@Cornstalks Muhtemelen insanlar, soru ne kadar geçerli, iyi tasarlanmış veya alakalı olursa olsun, "erken optimizasyon" olarak kolayca reddedebilecekleri sorulara o kadar iyi tepki vermiyorlar sanırım. Ben kendim için bunu yapıcı olmayan bir şekilde kapatmak için hiçbir sebep görmüyorum.
Christian Rau

13
(şimdi bir cevap yazamaz, kapalıdır, bu yüzden yorum yaparsınız) GCC ile programınız birden fazla iş parçacığı shared_ptrkullanmadığında refcount için atomik oplar kullanmaz. GCC'ye bir yama için gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html adresindeki (2) ' ye bakın , atomik olmayan uygulamanın, shared_ptraralarında paylaşılmayan nesneler için çok iş parçacıklı uygulamalarda bile kullanılmasına izin verir. İş Parçacığı. Yıllardır bu yama üzerinde oturuyorum ama sonunda onu GCC 4.9 için taahhüt etmeyi düşünüyorum
Jonathan Wakely

Yanıtlar:


107

1. std :: shared_ptr'nin atomik olmayan bir sürümü olup olmadığını merak ediyorum

Standart tarafından sağlanmamıştır. "3. taraf" kitaplığı tarafından sağlanan bir tane olabilir. Aslında, C ++ 11'den önce ve Boost'tan önce, herkes kendi referans sayılan akıllı işaretçi yazmış gibi görünüyordu (ben dahil).

2. İkinci sorum C ++ 11'de std :: shared_ptr'nin atomik olmayan bir versiyonu neden sağlanmadı?

Bu soru 2010'daki Rapperswil toplantısında tartışıldı. Konu, İsviçre tarafından Ulusal Organ Yorumu # 20 ile tanıtıldı. Sorunuzda sunduklarınız da dahil olmak üzere tartışmanın her iki tarafında da güçlü argümanlar vardı. Bununla birlikte, tartışmanın sonunda, oylama ezici bir çoğunlukla (ancak oybirliğiyle değil), senkronize edilmemiş (atomik olmayan) bir versiyonun eklenmesine karşıydı.shared_ptr .

Dahil edilen argümanlar:

  • Senkronize edilmemiş paylaşılan_tr ile yazılan kod, yolun aşağısındaki iş parçacıklı kodda kullanılmaya başlayabilir ve hiçbir uyarı olmaksızın hata ayıklanması zor sorunlara neden olabilir.

  • Referans saymada trafiğe ulaşmanın "tek yolu" olan bir "evrensel" shared_ptr'ye sahip olmanın avantajları vardır: Orijinal tekliften :

    Kullanılan özelliklerden bağımsız olarak aynı nesne türüne sahiptir ve üçüncü taraf kitaplıklar da dahil olmak üzere kitaplıklar arasında birlikte çalışabilirliği büyük ölçüde kolaylaştırır.

  • Atomun maliyeti sıfır olmasa da çok yüksek değil. Maliyet, atomik işlemleri kullanması gerekmeyen hareket inşası ve hareket ataması kullanımıyla azaltılır. Bu tür işlemler genellikle vector<shared_ptr<T>>silme ve yerleştirmede kullanılır.

  • İnsanların, gerçekten yapmak istedikleri buysa, kendi atomik olmayan referans sayılan akıllı işaretçilerini yazmalarını hiçbir şey yasaklamaz.

O gün Rapperswil'de LWG'den son söz şuydu:

CH 20'yi reddedin. Şu anda değişiklik yapmak için fikir birliği yok.


7
Vay canına, mükemmel, bilgi için teşekkürler! Tam da bulmayı umduğum türden bilgiler.
Cornstalks

> Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries. bu son derece tuhaf bir mantık. Üçüncü taraf kitaplıkları yine de kendi türlerini sağlayacak, öyleyse std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType>, vb. Biçiminde sağlasalar neden önemli olsun? her zaman kodunuzu kütüphanenin geri getirdiği şeye uyarlamanız gerekecek
Jean-Michaël Celerier

Kütüphaneye özgü türler söz konusu olduğunda bu doğrudur, ancak fikir şu ki, standart türlerin üçüncü taraf API'lerinde göründüğü birçok yer de vardır. Örneğin, kütüphanem bir std::shared_ptr<std::string>yere götürebilir . Başka birinin kütüphanesi de bu türü alırsa, arayanlar aynı dizeleri farklı temsiller arasında dönüştürme sıkıntısı veya ek yükü olmadan ikimize de iletebilir ve bu herkes için küçük bir kazançtır.
Jack O'Connor

52

Howard soruyu zaten iyi cevapladı ve Nicol birçok uyumsuz işaretçi yerine tek bir standart paylaşılan işaretçi türüne sahip olmanın faydaları hakkında bazı iyi noktalara değindi.

Komitenin kararına tamamen katılıyorum, ancak özel durumlarda senkronize edilmemiş shared_ptrbenzeri bir türü kullanmanın bazı faydaları olduğunu düşünüyorum , bu yüzden konuyu birkaç kez araştırdım.

Birden fazla iş parçacığı kullanmıyorsam veya birden çok iş parçacığı kullanıyorsam ancak işaretçi sahipliğini iş parçacıkları arasında paylaşmıyorsam, atomik bir akıllı işaretçi aşırı derecede önemlidir.

GCC ile, programınız birden fazla iş parçacığı kullanmadığında, shared_ptr refcount için atomik işlem kullanmaz. Bu, programın çok iş parçacıklı olup olmadığını saptayan sarmalayıcı işlevleri aracılığıyla referans sayılarını güncelleyerek yapılır (GNU / Linux'ta bu, programınlibpthread.so ) ve buna göre atomik veya atomik olmayan işlemlere gönderme yapan .

Yıllar önce fark ettim ki, GCC'ler shared_ptr<T>bir __shared_ptr<T, _LockPolicy>temel sınıf açısından uygulandığı için, temel sınıfı tek iş parçacıklı kilitleme ilkesiyle çok iş parçacıklı kodda bile açıkça kullanarak kullanmanın mümkün olduğunu __shared_ptr<T, __gnu_cxx::_S_single>. Maalesef, bu amaçlanan bir kullanım durumu olmadığı için, GCC 4.9'dan önce pek iyi çalışmadı ve bazı işlemler hala sarmalayıcı işlevlerini kullanıyordu ve bu nedenle, açık bir şekilde _S_singlepolitikayı talep etmenize rağmen atomik işlemlere gönderiliyordu . Http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html adresindeki 2. maddeye bakın.Daha fazla ayrıntı ve atomik olmayan uygulamanın çok iş parçacıklı uygulamalarda bile kullanılmasına izin veren bir GCC yaması için. Bu yamayı yıllarca kullandım ama sonunda GCC 4.9 için taahhüt ettim, bu da iş parçacığı açısından güvenli olmayan ancak biraz daha hızlı olan paylaşılan bir işaretçi türü tanımlamak için bunun gibi bir takma ad şablonu kullanmanıza izin veriyor:

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

Bu tür, birlikte çalışamaz std::shared_ptr<T>ve yalnızca shared_ptr_unsynchronizednesnelerin, kullanıcı tarafından sağlanan ek senkronizasyon olmadan evreler arasında asla paylaşılmayacağı garanti edildiğinde güvenle kullanılabilir .

Bu elbette tamamen taşınabilir değildir, ancak bazen sorun olmaz. Doğru önişlemci hack'leriyle, kodunuz diğer uygulamalarda yine de iyi çalışır, eğer shared_ptr_unsynchronized<T>bir takma ad ise shared_ptr<T>, GCC ile biraz daha hızlı olur.


4.9'dan önce bir GCC kullanıyorsanız, bunu _Sp_counted_base<_S_single>kendi kodunuza açık uzmanlıkları ekleyerek (ve __shared_ptr<T, _S_single>ODR ihlallerinden kaçınmak için hiç kimsenin uzmanlıkları dahil etmeden somutlaştırmamasını sağlayarak ) kullanabilirsiniz. Bu tür uzmanlıkları eklemek stdteknik olarak tanımsızdır, ancak pratikte çalışın, çünkü bu durumda uzmanlıkları GCC'ye eklemem veya kendi kodunuza eklemeniz arasında hiçbir fark yoktur.


2
Sadece merak ediyorum, şablon takma adı örneğinizde bir yazım hatası var mı? Yani, shared_ptr_unsynchronized = std :: __ shared_ptr <okuması gerektiğini düşünüyorum. Bu arada, bugün bunu std :: __ enable_shared_from_this ve std :: __ thin_ptr ile birlikte test ettim ve güzel çalışıyor gibi görünüyor (gcc 4.9 ve gcc 5.2). Atomik işlemlerin gerçekten atlanıp atlanmadığını görmek için kısa bir süre sonra profilini / parçalarına ayıracağım.
Carl Cook

Harika detaylar! Açıklandığı gibi Son zamanlarda, bir sorunu karşılaştığı bu sorunun eninde sonunda beni kaynak koduna bakmak için yapılmış olduğunu, std::shared_ptr, std::__shared_ptr, __default_lock_policyve bu tür. Bu cevap, koddan ne anladığımı doğruladı.
Nawaz

21

İkinci sorum, neden C ++ 11'de std :: shared_ptr'nin atomik olmayan bir sürümü sağlanmadı? (bir neden olduğunu varsayarak).

Bir kimse neden müdahaleci bir işaretçi olmadığını veya sahip olabileceği diğer olası paylaşılan işaretçilerin herhangi bir sayısını sorabilir.

shared_ptrBoost'tan aktarılan tasarım, minimum standart bir akıllı işaretçi dili oluşturmak olmuştur. Genel olarak konuşursak, bunu duvardan aşağı çekip kullanabilirsiniz. Genel olarak çok çeşitli uygulamalarda kullanılacak bir şey. Bir arayüze koyabilirsiniz ve muhtemelen iyi insanlar onu kullanmaya istekli olacaktır.

Diş açma, yalnızca gelecekte daha yaygın hale gelecektir. Aslında, zaman geçtikçe, diş açma genellikle performansa ulaşmak için birincil araçlardan biri olacaktır. Temel akıllı işaretçinin diş açmayı desteklemek için gereken minimum işi yapmasını zorunlu kılmak bu gerçeği kolaylaştırır.

Aralarında küçük varyasyonlara sahip yarım düzine akıllı işaretçi veya daha da kötüsü politika tabanlı bir akıllı işaretçiyi standart haline getirmek korkunç olurdu. Herkes en çok sevdiği işaretçiyi seçer ve diğerlerine yemin ederdi. Kimse başkasıyla iletişim kuramaz. Herkesin kendi türüne sahip olduğu C ++ dizelerindeki mevcut durumlar gibi olurdu. Yalnızca çok daha kötüsü, çünkü dizelerle birlikte çalışma, akıllı işaretçi sınıfları arasındaki birlikte çalışmadan çok daha kolaydır.

Boost ve buna ek olarak komite, kullanmak için belirli bir akıllı işaretçi seçti. İyi bir özellik dengesi sağladı ve pratikte yaygın ve yaygın olarak kullanıldı.

std::vectorbazı köşe durumlarda da çıplak dizilere kıyasla bazı verimsizliklere sahiptir. Bazı sınırlamaları vardır; bazı kullanımlar gerçekten vectorbir fırlatma ayırıcısı kullanmadan a boyutunda kesin bir sınıra sahip olmak ister . Ancak komite, vectorherkes için her şey olacak şekilde tasarlamadı . Çoğu uygulama için iyi bir varsayılan olacak şekilde tasarlanmıştır. İşe yaramayanlar, ihtiyaçlarına uygun bir alternatif yazabilirler.

Tıpkı akıllı bir işaretçi için yapabileceğiniz gibi, eğer shared_ptratomik bir yükse. Sonra tekrar, onları çok fazla kopyalamamayı da düşünebiliriz.


7
"Biri onları çok fazla kopyalamamayı da düşünebilir" için +1.
Ali

Bir profil oluşturucu bağlarsanız, özelsinizdir ve yukarıdaki gibi argümanları ayarlayabilirsiniz. Karşılanması zor bir operasyonel gereksiniminiz yoksa C ++ kullanmamalısınız. Sizin gibi tartışmak, yüksek performans veya düşük gecikme süresiyle ilgilenen herkesin C ++ 'ı evrensel olarak kötülemesini sağlamak için iyi bir yoldur.
Hans Malherbe

Netlik sağlamak için, cevabınızın üstündeki alıntıda " std :: shared_ptr'nin atomik olmayan bir versiyonu neden C ++ 11'de sağlanmadı?"
Charles Savoie

4

İş yerinde shared_ptr üzerine bir konuşma hazırlıyorum. Ayrı malloc (make_shared'in yapabilecekleri gibi) ve yukarıda bahsedilen shared_ptr_unsynchronized gibi kilit politikası için bir şablon parametresinden kaçınarak değiştirilmiş bir shared_ptr kullanıyorum. Programı şuradan kullanıyorum

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

bir test olarak, gereksiz shared_ptr kopyalarını temizledikten sonra. Program yalnızca ana iş parçacığını kullanır ve test argümanı gösterilir. Test ortamı, linuxmint 14 çalıştıran bir dizüstü bilgisayardır. Saniye cinsinden alınan süre:

make_shared değiştirilmiş destek ile test çalıştırması kurulum desteği (1.49) std
mt-güvensiz (11) 11.9 9 / 11.5 (-plikasyon açık) 8.4  
atom (11) 13.6 12.4 13.0  
mt-güvenli değil (12) 113.5 85.8 / 108.9 (-pthread açık) 81.5  
atom (12) 126.0 109.1 123.6  

Yalnızca 'std' sürümü -std = cxx11 kullanır ve -pthread muhtemelen g ++ __shared_ptr sınıfında lock_policy'yi değiştirir.

Bu rakamlardan atomik talimatların kod optimizasyonu üzerindeki etkisini görüyorum. Test senaryosu herhangi bir C ++ kapsayıcısı kullanmaz, ancak vector<shared_ptr<some_small_POD>>nesnenin iş parçacığı korumasına ihtiyacı yoksa büyük olasılıkla zarar görebilir. Boost daha az zarar görür çünkü ek malloc satır içi ve kod optimizasyonu miktarını sınırlar.

Henüz atom komutlarının ölçeklenebilirliğini stres testi için yeterli çekirdeğe sahip bir makine bulamadım, ancak std :: shared_ptr'yi yalnızca gerektiğinde kullanmak muhtemelen daha iyidir.


4

Boost, shared_ptratomik olmayan bir sağlar . Bu denir local_shared_ptrve boost'un akıllı işaretçiler kitaplığında bulunabilir.


İyi alıntı içeren kısa bir katı yanıt için +1, ancak bu işaretçi türü, hem bellek hem de çalışma süresi açısından, fazladan bir yönlendirme seviyesi nedeniyle maliyetli görünüyor (yerel-> paylaşılan-> ptr ve paylaşılan-> ptr).
Red.Wave

@ Red.Wave Dolaylılıkla ne demek istediğinizi ve performansı nasıl etkilediğini açıklayabilir misiniz? shared_ptrYerel olmasına rağmen yine de tezgahı olan bir olduğunu mu söylüyorsunuz ? Yoksa bununla ilgili başka bir sorun mu var? Doktorlar, tek farkın bunun atomik olmaması olduğunu söylüyor .
The Quantum Physicist

Her yerel ptr, orijinal paylaşılan ptr için bir sayı ve referans tutar. Bu nedenle, son noktaya herhangi bir erişim, yerelden paylaşılan ptr'ye, daha sonra pointee serbest bırakmaya ihtiyaç duyar. Böylece, paylaşılan ptr'den indirimler için yığılmış bir başka yönlendirme daha vardır. Ve bu da yükü artırır.
Red.Wave

@ Red.Wave Bu bilgiyi nereden alıyorsunuz? Bu: "Bu nedenle, son noktaya herhangi bir erişim yerelden paylaşılan ptr'ye bir başvuruya ihtiyaç duyar" biraz alıntıya ihtiyaç duyar. Bunu boost belgelerinde bulamadım. Yine, belgelerde gördüğüm şey, bunu söylediği local_shared_ptrve shared_ptratomik dışında aynı olduğuydu. Ben local_shared_ptryüksek performans gerektiren uygulamalarda kullandığım için söylediklerinizin doğru olup olmadığını gerçekten öğrenmekle ilgileniyorum .
The Quantum Physicist

3
@ Red.Wave Eğer gerçek kaynak koduna baktığımızda github.com/boostorg/smart_ptr/blob/... hiçbir çifte indirection yoktur göreceksiniz. Belgelerdeki bu paragraf sadece zihinsel bir modeldir.
Ilya Popov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.