it-swarm.dev

Akıllı işaretçiler varsa neden Çöp Toplama

Bugünlerde birçok dil çöp toplanıyor. Üçüncü taraflarca C++ için bile kullanılabilir. Ancak C++ 'da RAII ve akıllı işaretçiler vardır. Peki çöp toplama kullanmanın anlamı nedir? Fazladan bir şey mi yapıyor?

Ve C # gibi diğer dillerde, tüm referanslar akıllı işaretçiler olarak ele alınırsa (RAII'yi bir kenara bırakır), spesifikasyona ve uygulamaya göre, çöp toplayıcılarına hala ihtiyaç var mı? Hayır ise, neden böyle değil?

69
Gulshan

Peki, çöp toplama kullanmanın anlamı nedir?

Referans sayılan akıllı işaretçiler anlamına geldiğini varsayıyorum ve bunların çöp toplama (ilkel) bir formu olduğunu ve bu nedenle "referans sayılan akıllı işaretçilerden diğer çöp toplama biçimlerinin avantajları nelerdir?" yerine.

  • Doğruluk. Referans sayımı tek başına döngüleri sızdırır, bu nedenle referans sayımlı akıllı işaretçiler döngüleri yakalamak için başka teknikler eklenmedikçe genel olarak bellek sızdırırlar. Bu teknikler eklendikten sonra, referans sayımının basitlik avantajı ortadan kalkmıştır. Ayrıca, kapsama dayalı referans sayımı ve izleme GC'lerinin farklı zamanlarda değerler topladığını, bazen referans sayımının daha erken topladığını ve bazen izleme GC'lerinin daha erken topladığını unutmayın.

  • Verim. Akıllı işaretçiler, özellikle referans sayıları atomik olarak çarpıldığında çok iş parçacıklı uygulamalar bağlamında, çöp toplamanın en az verimli biçimlerinden biridir. Bunu hafifletmek için tasarlanmış gelişmiş referans sayma teknikleri vardır, ancak GC'leri izlemek hala üretim ortamlarında tercih edilen algoritmadır.

  • gecikme süresi. Tipik akıllı işaretçi uygulamaları yıkıcıların çığ açmasına izin vererek sınırsız duraklama sürelerine neden olur. Diğer çöp toplama biçimleri çok daha artımlıdır ve hatta gerçek zamanlı olabilir, ör. Baker'ın koşu bandı.

71
Jon Harrop

Kimse bu açıdan bakmadığından, sorunuzu yeniden ifade edeceğim: neden bir kütüphanede yapabiliyorsanız dile bir şey koyuyorsunuz? Belirli bir uygulamayı ve sözdizimsel ayrıntıları göz ardı ederek, GC/smart işaretçiler temelde bu sorunun özel bir örneğidir. Bir kütüphaneye uygulayabiliyorsanız neden bir çöp toplayıcıyı dilde tanımlayabilirsiniz?

Bu sorunun birkaç cevabı var. En önemlisi:

  1. Tüm kodların birlikte çalışabilmesini sağlarsınız. Bu, bence, kodun yeniden kullanılmasının ve kodunun Java/C #/Python/Ruby olana kadar paylaşım gerçekten işe yaramadı. Kütüphanelerin iletişim kurması gerekir ve sahip oldukları tek güvenilir paylaşılan dil, dil spesifikasyonunun kendisidir (ve bir dereceye kadar standart kütüphanesi). C++ 'ta kitaplıkları yeniden kullanmaya çalıştıysanız, muhtemelen standart bellek anlambiliminin neden olmadığı korkunç acıyı deneyimlemişsinizdir. Biraz lib'a bir yapı geçirmek istiyorum. Referans gönderir miyim? Işaretçi? scoped_ptr? smart_ptr? Mülkiyeti mi geçiyorum, değil mi? Bunu belirtmenin bir yolu var mı? Ya lib'in tahsis etmesi gerekiyorsa? Bir ayırıcı vermek zorunda mıyım? Bellek yönetimini dilin bir parçası haline getirmeyen C++, her kütüphane çiftini burada kendi özel stratejilerini müzakere etmeye zorlar ve hepsinin aynı fikirde olmasını sağlamak gerçekten zordur. GC bunu tam bir sorun yaratmaz.

  2. Etrafındaki sözdizimini tasarlayabilirsiniz. C++ bellek yönetiminin kendisini kapsamaya almadığından, kullanıcı düzeyi kodun tüm ayrıntıları ifade etmesine izin vermek için bir dizi sözdizimsel kanca sağlamak zorundadır. İşaretçileriniz, referanslarınız, const, kayıt silme işleçleri, dolaylı işlem işleçleri, adres, vs. Bu operatörlerin tümü kaybolur ve dil daha temiz ve basitleşir.

  3. Yüksek bir yatırım getirisi elde edersiniz. Herhangi bir kod parçasının ürettiği değer, onu kullanan kişi sayısıyla çarpılır. Bu, ne kadar çok kullanıcınız olursa, bir yazılım parçası için daha fazla harcama yapabileceğiniz anlamına gelir. Bir özelliği dile taşıdığınızda, dilin tüm kullanıcıları onu kullanır. Bu, yalnızca bu kullanıcıların bir alt kümesi tarafından kullanılan bir kitaplığa yapabileceğinizden daha fazla çaba harcayabileceğiniz anlamına gelir. Bu yüzden Java ve C # gibi diller kesinlikle birinci sınıf sanal makinelere ve fevkalade yüksek kaliteli çöp toplayıcılarına sahiptir: bunları geliştirme maliyeti milyonlarca kullanıcıya itfa edilmektedir.

66
munificent

Çöp toplama temel olarak, tahsis edilen nesnelerinize artık ulaşılamayan bir noktada otomatik olarak serbest bırakıldığı anlamına gelir.

Daha doğrusu, program için ulaşılamaz olduklarında serbest bırakılırlar, çünkü dairesel olarak referans alınan nesneler asla başka şekilde serbest bırakılmaz.

Akıllı işaretçiler sıradan bir işaretçi gibi davranıyor herhangi bir yapıya atıfta bulunur, ancak ek işlevler eklenir. Bunlar dahil ancak anlaşma ile sınırlı değildir, aynı zamanda yazma üzerine kopyala, bağlı kontroller, ...

Şimdi, belirttiğiniz gibi, akıllı işaretçiler kullanılabilir bir çöp toplama formu uygulamak için.

Ancak düşünce treni şu şekilde gider:

  1. Çöp toplama elverişli olduğu için çok güzel bir şey ve daha az şeyle ilgilenmem gerekiyor
  2. Bu nedenle: Kendi dilimde çöp toplama istiyorum
  3. Şimdi, GC'yi dilime nasıl aktarabilirim?

Tabii ki, başlangıçtan itibaren böyle tasarlayabilirsiniz. C # tasarlanmış çöp toplanacaktı, bu yüzden sadece new nesneniz ve referanslar kapsam dışına çıktığında serbest bırakılacak. Bunun nasıl yapıldığı derleyiciye kalmış.

Ancak C++ 'da amaçlanan hiçbir çöp toplama yoktu. Bir işaretçi int* p = new int; Ayırırsak ve kapsam dışına çıkarsa, p yığının kendisinden kaldırılır, ancak atanan bellekle kimse ilgilenmez.

Artık başlangıçta sahip olduğunuz tek şey deterministik yıkıcılar. Bir nesne oluşturulduğu kapsamdan çıktığında, yıkıcısı çağrılır. Şablonlar ve operatör aşırı yüklemesi ile birlikte, bir işaretçi gibi davranan, ancak ona bağlı kaynakları temizlemek için yıkıcı işlevini kullanan bir sarmalayıcı nesnesi tasarlayabilirsiniz (RAII). Buna akıllı işaretçi diyorsunuz.

Bu tamamen C++ 'ya özeldir: Operatör aşırı yüklenmesi, şablonlar, yıkıcılar, ... Bu özel dil durumunda, size istediğiniz GC'yi sağlamak için akıllı işaretçiler geliştirdiniz.

Ancak GC ile başlangıçtan itibaren bir dil tasarlarsanız, bu yalnızca bir uygulama ayrıntısıdır. Sadece nesnenin temizleneceğini ve derleyicinin bunu sizin için yapacağını söylüyorsunuz.

C++ gibi akıllı işaretçiler muhtemelen hiçbir belirleyici yıkımı olmayan C # gibi dillerde bile mümkün olmazdı (C #, belirli nesnelerde .Dispose() çağrısı için sözdizimsel şeker sağlayarak bunun etrafında çalışır). Referanssız kaynaklar nihayet GC tarafından geri kazanılacaktır, ancak tam olarak ne zaman gerçekleşeceği tanımlanmamıştır.

Ve bu da GC'nin işini daha verimli yapmasına izin verebilir. Dilde, üzerine yerleştirilmiş akıllı işaretçilerden daha derine yerleştirilmiş olan .NET GC, ör. bellek işlemlerini geciktirin ve bunları daha ucuz hale getirmek için bloklar halinde gerçekleştirin veya hatta hareket Nesnelerin ne sıklıkta erişildiğine bağlı olarak verimliliği artırmak için bellek.

36
Dario

Bellek yönetimi için kullanılan çöp toplama ve akıllı işaretçiler arasında bence iki büyük fark var:

  1. Akıllı işaretçiler döngüsel çöp toplayamazlar; çöp toplama
  2. Akıllı işaretçiler, başvuru işinde başvuru, kayıt silme ve yeniden yerleştirme anlarında tüm işleri yapar; çöp toplama gerekmez

Birincisi, GC'nin akıllı işaretçilerin atmayacağı çöpleri toplayacağı anlamına gelir; akıllı işaretçiler kullanıyorsanız, bu tür çöpleri oluşturmaktan kaçınmanız veya elle uğraşmaya hazır olmanız gerekir.

İkincisi, ne kadar akıllı akıllı işaretçiler olursa olsun, işlemlerinin programınızdaki çalışma iş parçacıklarını yavaşlatacağı anlamına gelir. Çöp toplama işi erteleyebilir ve diğer iş parçacıklarına taşıyabilir; genel olarak daha verimli olmasını sağlar (gerçekten de, modern bir GC'nin çalışma zamanı maliyeti, akıllı işaretçilerin ekstra yükü olmasa bile normal bir malloc/free sisteminden daha azdır) ve uygulama iş parçacığı yolu.

Şimdi, akıllı işaretçilerin, programlı yapılar olarak, çöp toplama kapsamının tamamen dışında olan diğer ilginç şeyleri (Dario'nun cevabına bakın) yapmak için kullanılabileceğini unutmayın. Bunları yapmak istiyorsanız, akıllı göstergelere ihtiyacınız olacak.

Ancak, bellek yönetimi amacıyla, çöp toplama yerine akıllı işaretçiler herhangi bir olasılık görmüyorum. Sadece o kadar iyi değiller.

4
Tom Anderson

Çöp toplama terimi, toplanacak çöp olduğunu ifade eder. C++ akıllı işaretçiler birden çok lezzet, en önemlisi unique_ptr geliyor. Unique_ptr temel olarak tek bir sahiplik ve kapsam oluşturma yapısıdır. İyi tasarlanmış bir kod parçasında, yığınla ayrılmış öğelerin çoğu normalde unique_ptr akıllı işaretçilerin arkasında bulunur ve bu kaynakların sahipliği her zaman iyi tanımlanır. Unique_ptr'de neredeyse hiç bir ek yük yoktur ve unique_ptr, geleneksel olarak insanları yönetilen dillere sürükleyen manuel bellek yönetimi sorunlarının çoğunu ortadan kaldırır. Eşzamanlı olarak çalışan daha fazla çekirdek artık daha iyi bir ürün haline geldiği için, kodu herhangi bir zamanda benzersiz ve iyi tanımlanmış sahiplik kullanmaya yönlendiren tasarım ilkeleri performans için daha önemli hale geliyor. Aktör hesaplama modelinin kullanılması, iş parçacıkları arasında minimum miktarda paylaşılan duruma sahip programların oluşturulmasına izin verir ve benzersiz sahiplik, yüksek performanslı sistemlerin ortaklar arasında paylaşılan yük olmadan birçok çekirdeğin verimli kullanılmasını sağlamada önemli bir rol oynar. evre verileri ve zımni muteks gereklilikleri.

İyi tasarlanmış bir programda bile, özellikle çok iş parçacıklı ortamlarda, her şey paylaşılan veri yapıları olmadan ifade edilemez ve gerçekten gerekli olan veri yapıları için iş parçacıklarının iletişim kurması gerekir. C++ RAII, tek iş parçacıklı bir kurulumda ömür boyu endişeler için oldukça iyi çalışır, çok iş parçacıklı bir kurulumda nesnelerin ömrü tamamen hiyerarşik olarak tanımlanamayabilir. Bu durumlar için shared_ptr kullanımı çözümün büyük bir bölümünü sunar. Bir kaynağın paylaşılan sahipliğini oluşturursunuz ve bu C++ 'da çöp gördüğümüz tek yerdir, ancak o kadar küçük miktarlarda, tam tasarlanmış çöp toplamadan ziyade paylaşılan-ptr'lerle' çöp 'koleksiyonunu uygulamak için daha uygun olarak düşünülmelidir. diğer dillerde uygulanmaktadır. C++ toplamak için o kadar çok 'çöp' yoktur.

Diğerleri tarafından belirtildiği gibi, referans sayılan akıllı işaretçiler bir çöp toplama biçimidir ve bunun bir büyük sorunu vardır. Çoğunlukla referans sayılan çöp toplama biçimlerinin dezavantajı olarak kullanılan örnek, birbirleriyle toplanmasını engelleyen nesne kümeleri oluşturan, birbirlerine akıllı işaretçilerle bağlanan yetimsiz veri yapılarının oluşturulması sorunudur. Hesaplamanın aktör modeline göre tasarlanan bir programdayken, veri yapıları, çoğunlukla büyük iş parçacığında kullanılan çok iş parçacıklı programlamaya geniş paylaşılan veri yaklaşımını kullandığınızda genellikle C++ 'da bu tür toplanabilir olmayan kümelerin ortaya çıkmasına izin vermez. Endüstrinin bu yetim kümeleri hızla gerçeğe dönüşebilir.

Yani özetlemek gerekirse, paylaşımlı işaretçi kullanımı ile, çok iş parçacıklı programlama için hesaplama yaklaşımının aktör modeli ile birlikte unique_ptr'in geniş kullanımı ve paylaşılan çöp toplama biçimlerinin diğerlerinden daha fazla paylaşılan paylaşılan_ptr kullanımı anlamına gelirseniz, ek avantajlar. Bununla birlikte, paylaşılan her şey yaklaşımı, her yerde paylaşılan_ptr ile sonuçlanmanızı sağlarsa, eşzamanlılık modellerini değiştirmeyi veya daha geniş sahiplik paylaşımına ve veri yapılarına eşzamanlı erişime daha fazla yönelik yönetilen bir dile geçmeyi düşünmelisiniz.

4
user1703394

Çoğu akıllı işaretçi referans sayımı kullanılarak uygulanır. Yani, bir nesneye başvuran her akıllı işaretçi, nesnelerin başvuru sayısını arttırır. Bu sayı sıfıra gittiğinde, nesne serbest bırakılır.

Sorun, dairesel referanslarınız varsa. Yani, A'nın B'ye, B'nin C'ye ve C'nin A'ya bir referansı vardır. Akıllı işaretçiler kullanıyorsanız, A, B & C ile ilişkili belleği boşaltmak için manuel olarak yapmanız gerekir oraya dairesel referansı "kes" (örneğin weak_ptr C++ 'da).

Çöp toplama (tipik olarak) oldukça farklı çalışır. Bugünlerde çoğu çöp toplayıcı laşılabilirlik testi kullanıyor. Yani, yığın üzerindeki tüm referanslara ve küresel olarak erişilebilir olanlara bakar ve daha sonra bu referansların başvurduğu her nesneyi izler ve nesneler onlar atıfta bulunur, vb. .

Bu şekilde, dairesel referanslar artık önemli değildir - A, B ve C'nin ikisi de laşılabilir olmadığı sürece, bellek geri kazanılabilir.

"Gerçek" çöp toplama işleminin başka avantajları da vardır. Örneğin, bellek ayırma işlemi son derece ucuzdur: işaretçiyi bellek bloğunun "sonuna" doğru artırın. Yeniden konumlandırma da sabit bir amortismana tabi tutulmuş maliyete sahiptir. Ancak elbette C++ gibi diller bellek yönetimini istediğiniz gibi uygulamanıza izin verir, böylece daha hızlı bir tahsis stratejisi oluşturabilirsiniz.

Tabii ki, C++ 'da yığınla ayrılan bellek miktarı tipik olarak C #/.NET gibi bir referans ağır dilden daha azdır. Ama bu gerçekten çöp toplama ve akıllı işaretçiler sorunu değil.

Her durumda, sorun kesilmiş ve kuru değil, diğerinden daha iyidir. Her birinin avantajları ve dezavantajları vardır.

2
Dean Harding

performans hakkında . Belleğin ayrılması çok fazla yönetim gerektirir. Ayrılma arka planda çalışırsa, ön plan işleminin performansı artar. Ne yazık ki, bellek ayırma tembel olamaz (tahsis edilen nesneler kutsal bir sonraki anda kullanılacaktır), ancak serbest bırakma nesneleri olabilir.

Büyük bir grup nesne ayırmak için C++ 'da (herhangi bir GC olmadan) deneyin, "merhaba" yazıp silin. Nesneleri boşaltmanın ne kadar sürdüğüne şaşıracaksınız.

Ayrıca, GNU libc belleği ayırmak için daha etkili araçlar sağlar, bkz engeller .

2
ern0

Çöp toplama daha verimli olabilir - temel olarak bellek yönetiminin yükünü 'toplu hale getirir' ve hepsini bir kerede yapar. Genel olarak bu, bellek ayırma işleminde daha az toplam CPU harcanmasına neden olur, ancak bu, bir noktada büyük bir ayırma etkinliği patlaması olacağınız anlamına gelir. GC düzgün bir şekilde tasarlanmamışsa, GC belleği ayırmaya çalışırken kullanıcı 'duraklama' olarak görülebilir. Çoğu modern GC, bunu en olumsuz koşullar dışında kullanıcı için görünmez tutmakta çok iyidir.

Akıllı işaretçiler (veya herhangi bir referans sayma şeması), koda bakmayı tam olarak beklediğinizde olma avantajına sahiptir (akıllı işaretçi kapsam dışına çıkar, bir şey silinir). Burada ve orada küçük tahsisat patlamaları var. Genel olarak, ayırma işleminde daha fazla CPU zamanı kullanabilirsiniz, ancak programınızda gerçekleşen tüm şeylere yayıldığından, kullanıcı tarafından görülebilir hale gelme olasılığı daha düşüktür (bazı canavar veri yapısının ayrılmasını engelleme).

Duyarlılığın önemli olduğu bir şey yapıyorsanız, akıllı işaretçilerin/ref sayımının işlerin tam olarak ne zaman gerçekleştiğini size bildirmesini öneririm, böylece kullanıcılarınız tarafından görülmesi muhtemel olan şeyleri kodlarken öğrenebilirsiniz. Bir GC ayarında, çöp toplayıcı üzerinde yalnızca en geçici kontrole sahip olursunuz ve sadece bir şey üzerinde çalışmak zorundasınız.

Öte yandan, toplam verim hedefinizse, GC tabanlı bir sistem, bellek yönetimi için gerekli kaynakları en aza indirdiği için çok daha iyi bir seçim olabilir.

Döngüler: Döngü sorununu önemli bir sorun olarak görmüyorum. Akıllı işaretçilerinizin bulunduğu bir sistemde, döngüleri olmayan veri yapılarına yönelirsiniz veya bu tür şeyleri nasıl bıraktığınız konusunda dikkatli olursunuz. Gerekirse, otomatik olarak imhayı otomatik olarak sağlamak için, sahip olunan nesnelerdeki döngüleri nasıl bozacağını bilen kaleci nesneler kullanılabilir. Bazı programlama alanlarında bu önemli olabilir, ancak günlük çalışmaların çoğu için bu önemsizdir.

2
Michael Kohne

Akıllı işaretçilerin bir numaralı sınırlaması, dairesel referanslara karşı her zaman yardım etmemesidir. Örneğin, A nesnesine akıllı bir işaretçi B nesnesine ve B nesnesi A nesnesine akıllı bir işaretçi depolar. İşaretçilerin herhangi birini sıfırlamadan birlikte bırakılırlarsa, hiçbir zaman yeniden konumlandırılmazlar.

Bunun nedeni, akıllı bir işaretçinin her iki nesnenin de program tarafından erişilememesi nedeniyle yukarıdaki senaryoda tetiklenmeyecek belirli bir eylem gerçekleştirmesi gerektiğidir. Çöp toplama başa çıkacak - nesnelerin programa erişilemediğini ve toplanacağını doğru bir şekilde belirleyecektir.

1
sharptooth

Bu bir spektrum.

Performansa sıkı sıkıya bağlı değilseniz ve Grind'i koymaya hazırsanız, Meclis veya c'ye girersiniz, doğru kararları vermeniz ve tüm bunu yapma özgürlüğünüz vardır, ancak bununla birlikte , tüm karışıklık özgürlüğü:

"Sana ne yapacağını söyleyeceğim, sen yap. Güven bana".

Çöp toplama spektrumun diğer ucudur. Çok az kontrole sahipsiniz, ancak sizin için halledilir:

"Sana ne istediğimi söyleyeceğim, sen bunu gerçekleştirirsin".

Bunun birçok avantajı vardır, çoğunlukla bir kaynağın artık ne zaman gerekli olmadığını bilmek söz konusu olduğunda güvenilir olmanıza gerek yoktur, ancak (burada yüzen cevapların bir kısmına rağmen) performans için iyi değildir ve performansın öngörülebilirliği. (Her şey gibi, size kontrol verilirse ve aptalca bir şey yaparsanız, daha kötü sonuçlara sahip olabilirsiniz. Ancak, derleme zamanında belleği serbest bırakabilmek için koşulların ne olduğunu bilmek, bir performans kazancı olarak kullanılamaz. naif ötesinde).

RAII, kapsam belirleme, ref sayma, vb. , bu spektrum boyunca daha fazla ilerlemenize izin veren yardımcılardır, ancak hepsi bu kadar değil. Bütün bunlar hala aktif kullanım gerektirir. Yine de, çöp toplama işleminin yapamayacağı şekilde bellek yönetimi ile etkileşim kurmanıza izin verir ve gerektirirler.

1
drjpizzle

Sonunda, her şeyin bir CPU yürütme talimatlarına bağlı olduğunu lütfen unutmayın. Bildiğim kadarıyla, tüm tüketici sınıfı CPU'ların, bellekte belirli bir yerde verilerin depolanmasını gerektiren talimat setleri vardır ve söz konusu verilere işaretçileriniz vardır. Temel seviyede sahip olduğunuz her şey bu.

Çöp toplama, taşınmış olabilecek verilere referanslar, yığın sıkıştırma vb. İle ilgili her şey yukarıdaki "adres işaretleyicili bellek yığını" paradigması tarafından verilen kısıtlamalar dahilinde çalışmaktadır. Akıllı işaretçilerle aynı şey - kodu gerçek donanımda çalıştırmanız gerekir.

0
user1249