Referans sayma akıllı işaretçiler neden bu kadar popüler?


52

Görebildiğim gibi, akıllı işaretçiler birçok gerçek dünyadaki C ++ projesinde yaygın olarak kullanılıyor.

Her ne kadar bazı akıllı işaretçiler RAII ve mülkiyet aktarımlarını desteklemede açıkça faydalı olsalar da, paylaşılan işaretçileri varsayılan olarak "çöp toplama" yöntemi olarak kullanma eğilimi vardır , böylece programcının bu kadar tahsisat hakkında düşünmesi gerekmez. .

Paylaşılan işaretçiler neden Boehm GC gibi uygun bir çöp toplayıcıyı entegre etmekten daha popülerdir ? (Ya da onların gerçek GC'lerden daha popüler oldukları konusunda hemfikir misiniz?)

Konvansiyonel GC'lerin referans saymaya göre iki avantajı olduğunu biliyorum:

  • Konvansiyonel GC algoritmaları referans çevrimleriyle bir problemi yoktur .
  • Referans sayımı genellikle uygun bir GC'den daha yavaştır .

Referans sayma akıllı işaretçiler kullanmanın nedenleri nelerdir?


6
Sadece bunun yanlış bir varsayılan kullanım olduğuna dair bir yorum eklerdim: çoğu durumda, std::unique_ptryeterlidir ve bu nedenle çalışma zamanı performansı açısından ham göstergelere sıfır ek yükü vardır. std::shared_ptrHer yerde kullanarak , sahiplik semantiğini de gizleyerek, otomatik kaynak yönetimi dışındaki akıllı işaretçilerin en önemli avantajlarından birini kaybettiniz - kodun arkasındaki amacın net bir şekilde anlaşılması.
Matt,

2
Üzgünüz, ancak buradaki cevaplar tamamen yanlış. Referans sayımının daha yüksek ek yükleri (işaret biti yerine bir yavaş sayma ve daha yavaş çalışma süresi performansı), azalmalar çığladığında sınırsız duraklama süreleri vardır ve Cheney yarı boşluğu olan daha karmaşık bir yapıya sahip değildir.
Jon Harrop

Yanıtlar:


57

Çöp toplama işleminde referans saymanın bazı avantajları:

  1. Düşük havai. Çöp toplayıcılar oldukça müdahaleci olabilir (örneğin, bir çöp toplama döngüsü işlenirken programınızın öngörülemeyen zamanlarda donmasına neden olabilir) ve oldukça fazla bellek yoğunlaştırılabilir (örneğin, işleminizin bellek ayak izi gereksiz yere çöp toplanmadan önce birçok megabaytla büyür)

  2. Daha fazla tahmin edilebilir davranış. Referans sayma işleminde, son referansı kaybolduğu anda nesnenizin serbest bırakılacağı garanti edilir. Öte yandan, çöp toplama işleminde, sistem etrafa geldiğinde nesneniz "bazen" serbest bırakılır. RAM için bu genellikle masaüstlerinde veya çok az yüklü sunucularda büyük bir sorun değildir, ancak diğer kaynaklar için (örn. Dosya tanıtıcıları), daha sonra olası çatışmaları önlemek için genellikle en kısa zamanda kapalı olmaları gerekir.

  3. Daha basit. Referans sayımı birkaç dakika içinde açıklanabilir ve bir veya iki saat içinde uygulanabilir. Çöp toplayıcıları, özellikle iyi performans gösterenler, son derece karmaşıktır ve çoğu insan onları anlamamaktadır.

  4. Standart. C ++ referans sayımı (paylaşılan_ptr aracılığıyla) ve STL'deki dostları içerir, bu da çoğu C ++ programcının aşina olduğu ve çoğu C ++ kodunun çalışacağı anlamına gelir. Herhangi bir standart C ++ çöp toplayıcısı yoktur, ancak bu, birini seçmeniz ve kullanım durumunuz için iyi çalışacağını ummanız gerektiği anlamına gelir - ve değilse, düzeltmek sizin için sorun değil, dilin değil.

Referans saymanın iddia ettiği dezavantajlara gelince - döngüleri tespit etmemek bir konudur, ancak referans saymayı kullanmanın son on yılında hiç şahsen rastlamadığım bir konudur. Çoğu veri yapısı doğal olarak döngüseldir ve döngüsel referanslara (örneğin bir ağaç düğümündeki ana işaretçi) ihtiyaç duyduğunuz bir durumla karşılaşırsanız, sadece "geriye doğru yön" için bir zayıf_ptr veya ham C işaretçi kullanabilirsiniz. Veri yapılarınızı tasarlarken ortaya çıkan olası sorunun farkında olduğunuz sürece sorun olmaz.

Performans gelince, referans sayma performansıyla hiçbir zaman bir sorunum olmadı. Çöp toplama performansında, özellikle GC'nin maruz kalabileceği rastgele donma olayları ile ilgili problemlerim var, bunlara tek çözümün ("nesneleri tahsis etmeyin") "GC kullanmayın" olarak değiştirilebileceği gibi .


16
Naif referans sayma uygulamaları tipik olarak gecikme pahasına üretim GC'lerinden (% 30-40) daha düşük verim elde eder. Boşluk, geri sayım için daha az bit kullanma ve kaçana kadar izleme nesnelerinden kaçınma gibi optimizasyonlarla kapatılabilir; C ++ bunu en çok make_sharedgeri döndüğünde doğal olarak yapar . Yine de, gecikme gerçek zamanlı uygulamalarda daha büyük bir sorun olma eğilimindedir, ancak genel olarak verim genel olarak daha önemlidir, bu nedenle GC'lerin izlenmesi bu kadar yaygın şekilde kullanılmaktadır. Onlardan fena konuşacak kadar hızlı olmazdım.
Jon Purdy

3
Daha basit açısından: Ben 'daha basit' kelime oyunu istiyorum hayata geçirilmesinin toplam miktarının kodu için evet, ama daha basit kullanan birisi RC nasıl kullanılacağı (söylüyorum karşılaştırın yapın: o bu nesneleri oluştururken ve bu onları yok iken ' ) nasıl yapılır (doğal olarak, genellikle yeterlidir) GC ('...') kullanır.
AakashM

4
"Referans sayma işleminde, son referansı kaybolduğu anda nesnenizin serbest bırakılacağı garanti edilir". Bu yaygın bir yanılgıdır. flyingfrogblog.blogspot.co.uk/2013/10/…
Jon Harrop

4
@JonHarrop: Bu blog yazısı çok yanlış. Tüm yorumları, özellikle de sonuncusunu da okumalısınız .
Deduplicator

3
@JonHarrop: Evet, var. Ömrünün kapanış ayracına kadar giden tam kapsam olduğunu anlamıyor. Değişken tekrar kullanılmazsa, yorumlara göre sadece bazen işe yarayan F # 'daki optimizasyon daha önce ömrünü sonlandırıyor. Doğal olarak kendi tehlikeleri var.
Deduplicator

26

Bir GC'den iyi performans elde etmek için, GC'nin bellekteki nesneleri taşıyabilmesi gerekir. Doğrudan bellek konumlarıyla etkileşime girebileceğiniz C ++ gibi bir dilde, bu oldukça imkansızdır. (Microsoft C ++ / CLR, GC tarafından yönetilen işaretçiler için yeni bir sözdizimi getirdiğinden ve dolayısıyla farklı bir dil olduğu için sayılmaz.)

Şık bir fikir olsa da, Boehm GC her iki dünyanın da en kötüsüdür: iyi bir GC'den daha yavaş olan bir malloc'a () ihtiyacınız vardır ve böylece nesillerdeki bir GC'nin karşılık gelen performans artışı olmadan deterministik tahsis / ayrılma davranışını kaybedersiniz . Üstelik zorunlu olarak muhafazakar, bu yüzden yine de tüm çöplerinizi toplamayacaktır.

İyi, iyi ayarlanmış bir GC harika bir şey olabilir. Ancak C ++ gibi bir dilde, kazançlar minimum düzeyde ve maliyetler de buna değmez.

Bununla birlikte, C ++ 11'in daha popüler hale gelmesiyle, lambdaların ve yakalama anlambilimlerinin C ++ topluluğunun aynı tür tahsislere yönelmeye başlayıp başlamadıklarını ve Lisp topluluğunun ilk kez GC'leri icat etmelerine neden olan yaşam boyu sorunlarına karşı çıkıp çıkmadığını görmek ilginç olacaktır. yer, yerleştirmek.

Ayrıca bakınız StackOverflow ile ilgili soruya cevabım .


6
Boehm GC, bazen genel olarak teknolojinin kötü bir ilk izlenimini sağlayarak, C ve C ++ programcıları arasında geleneksel GC'ye karşı müdahalenin kişisel olarak ne kadar sorumlu olduğunu merak ettim .
Leushenko

@Leushenko İyi dedi. Bu noktada, Boehm gc'nin "uygun" gc olarak adlandırıldığı, yavaş ve pratik olarak sızıntısının garanti edilmediği gerçeğini göz ardı ettiği bir soru var. Bu soruyu birisinin c ++ uygulamasında değerli bir amaç gibi görünen paylaşılan_ptr için python tarzı döngü kesici kullanıp kullanmadığını araştırırken buldum.
user4815162342 17:15

4

Görebildiğim gibi, akıllı işaretçiler birçok gerçek dünyadaki C ++ projesinde yaygın olarak kullanılıyor.

Doğru, ancak nesnel olarak, kodun büyük bir kısmı artık çöp toplayıcıları ile modern dillerde yazılmıştır.

Her ne kadar bazı akıllı işaretçiler RAII ve mülkiyet aktarımlarını desteklemede açıkça faydalı olsa da, programcının bu kadar tahsisat hakkında düşünmek zorunda kalmaması için, varsayılan olarak "çöp toplama" yöntemi olarak paylaşılan işaretçileri kullanma eğilimi vardır. .

Bu kötü bir fikir çünkü hala döngü hakkında endişelenmen gerekiyor.

Paylaşılan işaretçiler neden Boehm GC gibi uygun bir çöp toplayıcıyı entegre etmekten daha popülerdir? (Ya da onların gerçek GC'lerden daha popüler oldukları konusunda hemfikir misiniz?)

Vay canına, senin düşünce tarzında bir sürü yanlış var.

  1. Boehm’in GC’si herhangi bir anlamda “uygun” bir GC değildir. Bu gerçekten korkunç. Muhafazakar olduğundan sızıntı yapar ve tasarım açısından verimsizdir. Bakınız: http://flyingfrogblog.blogspot.co.uk/search/label/boehm

  2. Paylaşılan işaretçiler, nesnel olarak, GC kadar popüler değildir, çünkü geliştiricilerin büyük çoğunluğu şimdi GC'd dillerini kullanmaktadır ve ortak işaretçilere gerek yoktur. İş piyasasında Java ve Javascript'e C ++ 'a kıyasla.

  3. C ++ 'ı dikkate almayı kısıtlıyor gibi görünüyorsunuz, çünkü GC'nin somut bir mesele olduğunu düşünüyorum. Değil ( iyi bir GC almanın tek yolu, dili ve VM'yi baştan tasarlamaktır), bu yüzden seçim yanlılığı ortaya çıkarıyorsunuz. Gerçekten doğru çöp toplama isteyenler C ++ 'a sadık kalmazlar.

Referans sayma akıllı işaretçiler kullanmanın nedenleri nelerdir?

C ++ ile sınırlandırılmışsınız, ancak otomatik bellek yönetiminiz olsaydı.


7
Um, bu bir soru etiketli var c ++ hakkında C ++ özelliklerini anlatıyor. Açıkçası, herhangi genel ifadeler bahsediyoruz içinde C ++ kodunun, programlama değil bütünüyle. Bu nedenle "nesnel olarak" çöp toplama işlemi, C ++ dünyasının dışında kullanımda olabilir; bu, eldeki soru ile nihayetinde ilgisizdir.
Nicol Bolas,

2
Son satırınız açıkça yanlıştır: C ++ 'tasınız ve GC ile başa çıkmak zorunda olmadığınızdan ve kaynakların serbest bırakılmasından dolayı geciktiğinize sevindiniz. Apple’ın GC’yi sevmemesinin bir nedeni var ve GC'd’lerin dilleri için en önemli rehber şudur: Boş kaynaklardan bir kaç tane yoksa ya da yardım edemediğin sürece herhangi bir çöp atma.
Deduplicator

3
@JonHarrop: Öyleyse, her iki tarafın da avantajına oynamak için açıkça seçilmeyen GC içeren ve içermeyen eşdeğer küçük programları karşılaştırın. Hangisinin daha fazla belleğe ihtiyacı var?
Deduplicator

1
@Deduplicator: Her iki sonucu da veren programları öngörebilirim. Referans sayımı, program yığın tutmaya devam etmek için tasarlandığında GC'yi çocuk yuvasına ulaşana kadar (örn. Bir sıralar listesi) saklamak için tasarlandığında GC'yi geride bırakacaktır, çünkü bu nesiller bir GC için patolojik performanstır ve en fazla yüzen çöpü üretecektir. Çöp toplama işleminin izlenmesi, birçok küçük nesne olduğunda ve yaşam süreleri kısa olduğunda ancak statik olarak iyi bilinmediğinde, kapsamı temelli referans sayımından daha az bellek gerektirir; bu nedenle, tamamen işlevsel veri yapıları kullanan bir mantık programı gibi bir şey.
Jon Harrop

3
@JonHarrop: C ++ konuşuyorsanız GC (izleme veya herhangi bir şekilde) ve RAII ile kastediyordum. Referans sayımı içerir, ancak yalnızca yararlı olduğunda. Veya bir Swift programı ile karşılaştırabilirsiniz.
Deduplicator

3

MacOS X ve iOS'de ve Objective-C veya Swift kullanan geliştiricilerin referans sayımı popülerdir, çünkü otomatik olarak kullanılır ve Apple, artık desteklemediğinden çöp toplama kullanımı oldukça azalmıştır ( çöp toplama işlemi bir sonraki MacOS X sürümünde yayınlanacak ve çöp toplama iOS'ta hiçbir zaman uygulanmamıştır). Aslında, çöp toplama kolunu kullanılabildiğinde kullanan çok fazla yazılım olduğundan şüpheliyim.

Çöp toplama işleminden kurtulmanın nedeni: İşaretçilerin, çöp toplayıcı tarafından erişilemeyen alanlara "kaçabilecekleri" C tarzı bir ortamda hiçbir zaman güvenilir bir şekilde çalışmadı. Apple, referans sayımının daha hızlı olduğuna inanıyor ve inanıyor. (Burada göreceli hız konusunda herhangi bir iddiada bulunabilirsiniz, ancak kimse Apple'ı ikna edemedi). Ve sonunda, hiç kimse çöp toplama kullanmamış.

Herhangi bir MacOS X veya iOS geliştiricisinin öğrendiği ilk şey, referans döngülerini nasıl ele alacağınızdır, bu yüzden gerçek bir geliştirici için sorun değildir.


Anladığım kadarıyla, karar veren C benzeri bir ortam değildi, ancak GC'nin belirsiz olduğunu ve kabul edilebilir bir performans elde etmek için çok daha fazla belleğe ihtiyacı olduğunu ve her zaman biraz az olan sunucu / masaüstünün dışında olduğunu belirtti.
Deduplicator

Çöp toplayıcısının neden hala kullandığım bir nesneyi imha ettiği (
çökmeye

Oh evet, bu da olur. Sonunda nedenini öğrendin mi?
Deduplicator

Evet, bir boşluğu * "bağlam" olarak ilettiğiniz birçok Unix işlevinden biriydi; bu daha sonra geri arama işlevinde size geri verildi; boşluk * gerçekten bir Objective-C nesnesiydi ve çöp toplayıcı, nesnenin Unix çağrısında saklandığını farketmedi. Geri arama denir, boşuna * nesneler * kaboom!
gnasher729

2

C ++ 'daki çöp toplama işleminin en büyük dezavantajı, doğru anlaşılmasının imkansız olmasıdır:

  • C ++ 'da işaretçiler kendi duvarları olan topluluklarında yaşamaz, diğer verilerle karıştırılır. Dolayısıyla, bir işaretçiyi, geçerli bir işaretçi olarak yorumlanabilecek bir bit desenine sahip olan diğer verilerden ayırt edemezsiniz.

    Sonuç: Herhangi bir C ++ çöp toplayıcısı, toplanması gereken nesneleri sızdıracak.

  • C ++ 'da, işaretçileri çıkarmak için işaretçi aritmetiği yapabilirsiniz. Bu nedenle, bir bloğun başlangıcına bir işaretçi bulamazsanız, bu o bloğa başvuru yapılamayacağı anlamına gelmez.

    Sonuç: Herhangi bir C ++ çöp toplayıcısının bu ayarlamaları hesaba katması gerekir; bu, bloktan sonra da dahil olmak üzere bir blok içindeki herhangi bir yeri işaret eden herhangi bir bit dizisini bloğa başvuran geçerli bir işaretçi olarak kabul eder.

    Not: Hiçbir C ++ çöp toplayıcısı aşağıdaki gibi numaralarla kod işleyemez:

    int* array = new int[7];
    array--;    //undefined behavior, but people may be tempted anyway...
    for(int i = 1; i <= 7; i++) array[i] = i;
    

    Doğru, bu tanımsız davranışa neden olur. Ancak, varolan bazı kodlar, bunun için iyi olandan daha akıllıdır ve bir çöp toplayıcı tarafından ön bırakma işlemini tetikleyebilir.


2
" diğer verilerle karıştırılmışlar. " Diğer verilerle "karıştırılmış" olmaları o kadar da değil. Neyin imleci olup olmadığını görmek için C ++ tip sistemini kullanmak kolaydır. Sorun, işaretçilerin sıklıkla başka veriler haline gelmesidir . Bir işaretleyiciyi bir tamsayıda gizlemek, birçok C tarzı API için ne yazık ki yaygın bir araçtır.
Nicol Bolas,

1
Bir çöp toplayıcısını c ++ 'da berbat etmek için tanımsız davranışa bile ihtiyacınız yok. Örneğin, bir işaretçiyi bir dosyaya seri hale getirebilir ve daha sonra okuyabilirsiniz. Bu arada, işleminiz bu işaretçiyi adres alanının hiçbir yerinde içermeyebilir, böylece çöp toplayıcı bu nesneyi toplayabilir ve ardından işaretçiyi seri hale getirdiğinizde ...
Bwmat

@ Bwmat "Çift"? Bunun gibi bir dosyaya işaretçiler yazmak biraz zor görünüyor. Her neyse, aynı ciddi problem, nesneleri yığınlamak için işaretçileri rahatsız eder, işaretçiyi kodun başka bir yerindeki dosyadan tekrar okuduğunuzda giderler! Geçersiz işaretçi değerini deserializing olduğunu tanımsız davranış, bunu yapma.
hyde

Elbette, böyle bir şey yapıyorsanız dikkatli olmanız gerekir. Genel olarak, bir çöp toplayıcısının c ++ tüm durumlarda (dili değiştirmeden) 'düzgün' çalışamayacağı bir örnek olması gerekiyordu
Bwmat

1
@ gnasher729: Ehm, hayır? Geçmiş-uç-işaretçiler tamamen iyi mi?
Deduplicator
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.