Sistem Programcıları İçin Garbage Collection (2023)
(bitbashing.io)- Çekirdekler ve sürücüler gibi performans ve eşzamanlılığın önemli olduğu kodlarda bile bellek serbest bırakma basit bir
free()çağrısıyla bitmez; kilitsiz paylaşım teknikleri gerekebilir - RCU (Read, Copy, Update), sık okunan ve nadiren değişen verileri kopyalayıp işaretçiyi atomik olarak değiştirerek okuma yolunu engellemeyen bir yöntemdir
- Önceki sürüm hemen
deleteedilirse hâlâ okuyan thread'lerde use-after-free oluşabilir; bu yüzden RCU okuma aralıklarını izler ve serbest bırakmayı güvenli bir ana kadar erteler - RCU Linux'ta on binlerce yerde kullanılır; Folly C++ kütüphanesinde ve Rust'ın
crossbeam-epochpaketinde de bulunur. Kullanımda olup olmamasına göre daha sonra temizlenmesi bakımından GC'ye benzer bir yapıya sahiptir - Elle bellek yönetiminin her zaman daha hızlı ve öngörülebilir olduğu ikili karşıtlığı zayıftır;
free(), referans sayımı ve OS bellek davranışları da maliyet ve belirsizlik taşıdığı için modern GC de sistem programlamanın araçlarından biri olabilir
Çekirdek kodu neden RCU gibi teknikler kullanır
- İşletim sistemleri, her gün çalışan programlar arasında performansa duyarlılığı yüksek olanlar arasında yer alır
- OS hızlandığında kullanıcılar daha fazla hesaplama yapabildiği için çekirdek ve sürücü geliştiricileri kod optimizasyonuna büyük emek harcar
- İşletim sistemi, kullanıcı alanındaki süreç ve thread'lerin yanı sıra çekirdeğin kendi içindeki birçok thread'i ve donanım kesme işleyicilerini de yönetmek zorundadır
- Gecikme arttığında kullanıcının zamanı çalındığı için çekirdek kodunda thread'ler arasında veriyi kilit kullanmadan paylaşmaya yönelik çeşitli teknikler ortaya çıkmıştır
RCU'nun temel işleyişi
- RCU (Read, Copy, Update) çok sık okunan ama nadiren yazılan verilere uygun bir yöntemdir
- O anda bağlı olan USB aygıtları kümesi gibi neredeyse hiç değişmeyen ama değişmesi mümkün olan veriler buna örnektir
- Değişiklik atomik olarak gerçekleşmeli ve hâlihazırda okuyan okuyucuları engellememelidir
- Yazar paylaşılan durumu şu sırayla günceller
- Mevcut veriyi işaretçiden okur
- Mevcut veriyi kopyalar, gerekli değişikliği uygular ve yeni sürümü oluşturur
- İşaretçiyi atomik olarak güncelleyerek yeni sürümü göstermesini sağlar
- Okuyucu yalnızca paylaşılan işaretçiyi okuduğu için okuma yolu basittir ve beklemeden çalışır
- Bu yöntem kullanımı kolay ve wait-free'dir; ancak eski sürümler temizlenmezse bellek sızıntısı oluşur
Hemen serbest bırakılamayan eski sürüm
- İşaretçi yeni sürümle değiştirildikten hemen sonra eski sürüm
deleteedilirse use-after-free riski doğar - Kilitsiz çalıştığı için yazar, eski sürümü hâlâ okuyan okuyucu olup olmadığını bilemez
- Okuyucu,
rcu_read_lock()vercu_read_unlock()ile okuma tarafındaki kritik bölgeyi işaretleyebilir- Okuyucu yine de bloklanmaz
- Yazar, bu okuyucular çıkana kadar eski veriyi kaldırmaz
rcu_synchronize()tüm okuyucuların ortadan kalkmasını değil, yalnızca eski sürümü görme ihtimali olan eski okuyucuların bitmesini beklemek zorundadır- Yeni işaretçiyi gören okuyucu yeni sürümü kullanacağı için eski sürümün ömrüyle ilgisi yoktur
Ertelenmiş serbest bırakma ve GC biçimi
- Yazar güncelleme fonksiyonu içinde beklemek zorunda olmasa da eski veri bir gün güvenli biçimde serbest bırakılırsa kod doğru çalışır
rcu_defer(old)benzeri bir yöntem, mevcut okuyucular kritik bölgeden çıktıktan sonra herhangi bir zamandaold'un serbest bırakılmasını sağlayabilir- Özel bir thread'in referans verilmeyen eski sürümleri periyodik olarak temizlemesi nesilsel GC'ye benzer
- RCU bir düşünce deneyi değil, pratikte yaygın kullanılan bir tekniktir
- Linux RCU'yu on binlerce yerde kullanır
- Facebook'un Folly C++ library içinde RCU sağlanır
- Rust'ta
crossbeam-epochadıyla kullanılır ve popüler eşzamanlılık kütüphanelerinin temelini oluşturur
- RCU'nun “gerçek GC” olup olmadığına dair sınıflandırma tartışmasından daha önemli olan nokta, belleğin kullanımda olup olmadığına göre daha sonra temizlenmesi yapısının GC ile aynı olmasıdır
Elle serbest bırakmanın gizli maliyetleri
- GC'nin elle bellek yönetiminden özünde daha az verimli olduğu yönündeki yaygın kanı, uygulama ayrıntılarına bakıldığında zayıflar
-
free()bedava değildir- Genel amaçlı bellek ayırıcıları, çekirdekten alınan sayfalar, boyuta göre bucket bölümleri ve kullanımda olan bucket'lar gibi içsel global durumu yönetmek zorundadır
- Birden fazla thread ayırıcı durumunu kilitlemeye çalıştığında çekişme oluşabilir
- jemalloc gibi thread-local havuzlar kullanılsa bile bunları senkronize etmek için ek kod gerekir
-
RAII ve lifetime da ayırıcı maliyetini ortadan kaldırmaz
- Rust'ın lifetime'ları veya C++'ın RAII yaklaşımı, belleğin otomatik serbest bırakılması ve doğruluk açısından yardımcı olur; ancak ayırıcının iç yapısındaki karmaşıklığı ortadan kaldırmaz
- Birçok senaryoda
shared_ptrveyaArckullanımına dönmek gerekir - Referans sayısı adlı ek metadata gerekir ve bu değer çekirdekler ile cache arasında gidip gelerek maliyet yaratabilir
- Liveness grafiğindeki döngülerin sızıntıya yol açmasına da neden olabilir
-
GC'nin sunabileceği optimizasyonlar da vardır
- Taşınabilir nesilsel GC, heap'i periyodik olarak yeniden sıkıştırır
- Ayırma, işaretçi artırmaya yakın hâle gelerek yüksek throughput sağlayabilir
- Sıralı ayırmanın locality'si iyileştiği için cache performansına da yardımcı olur
Bellek yönetimi üzerinde denetim yanılgısı
- GC'ye karşı çıkan birçok geliştirici soft real-time sistemler geliştirir
- Video oyunlarında FPS veya streaming codec'lerde sıkıştırma performansı gibi mümkün olan en hızlı çalışmayı isterler
- Ancak ara sıra 1 milisaniye fazla sürmesi sistemi bozan ya da insan öldüren hard latency gereksinimleri değildir
- Programcının bellek yönetiminin ne zaman gerçekleşeceğine karar verebildiği inancı basit değildir
- İşletim sistemi donanım etkileşimini soyutlar
- Linux varsayılan olarak bellek isteği sırasında neredeyse hiçbir şey yapmaz ve belleği gerçekten kullanılmak istendiğinde verebilir
madvise(), memory-mapped I/O ve dosya sistemi cache'i işin içine girince “neyin ne zaman ayrıldığına” dair basit bir cevap kalmaz- Kötü bir günde basit bir işaretçi erişimi disk I/O'ya bile dönüşebilir
- Programcının bellek yönetimi için durmaya uygun anı her zaman bildiği inancı da sınırlıdır
- Video oyunu yükleme ekranları gibi net durumlar vardır
- Birçok yazılımda tek yanıt, daha önemli bir işle meşgul olunmayan zamandır
shared_ptrveArckullanan tekil kodlar, son sahip olup temizliği üstlenip üstlenmeyeceklerini önceden bilemez
free()çağrısının belleği hemen OS'e geri verdiği inancı da her zaman doğru değildir- Bellek OS'ten sayfa birimleriyle ayrılır
- Ayırıcılar çoğu zaman sayfaları program bitene kadar elde tutup yeniden kullanmak ister
- OS swap aracılığıyla sayfaları geri alabilir
GC'yi sistem programlama aracı olarak görmenin nedeni
- Her yazılım GC'den fayda görmez
- Ancak 2024'e yakın dönemde bile sistem programcıları arasında GC tartışması yanlış ikili karşıtlıklara ve korku, belirsizlik, şüpheye kolayca gömülebilir
- GC kullanan dillerin elle bellek yönetimi yapan dillerden “açıkça” daha yavaş olduğu düşüncesi gerçek olmaktan çok ideolojiye yakındır
- Hayati önem taşıyan sistemler geliştiren ekiplerde bile, neredeyse her satırda ayırma yapan GC'li bir dille mikrosaniyenin altında gecikme sunulan örnekler vardır
- Sistemin bir bölümünün mutlaka
nclock cycle içinde çalışması gerekiyorsa yalnızca o belirli bölüm GC dışı kod veya donanım olarak ayrılabilir - GC her derde deva değildir; ancak korkmadan kullanılabilecek araç kutusundaki araçlardan biridir
1 yorum
Hacker News yorumları
Umut vadeden modern paralel çöp toplama teknikleri arasında MPL veya MaPLe ve onun yeni Automatic Management of Parallelism yaklaşımına bakılabilir
POPL 2024 distinguished paper award ve ACM SIGPLAN dissertation award 2023 kazanan bu çalışmanın özü iki nokta: a) disentanglement temelli, kanıtlanabilir biçimde verimli paralel çöp toplama, b) kanıtlanabilir biçimde verimli otomatik ayrıntı düzeyi denetimi
[1] MaPLe (MPL): https://github.com/MPLLang/mpl
[2] Automatic Parallelism Management: https://dl.acm.org/doi/10.1145/3632880
Hem bağlantısı verilen makale var hem de bölge tabanlı bellek yönetiminin erken kullanıcıları ve öncülerinden biri olan MLKit var
Örneğin böyle bir yaklaşım Go'nun çöp toplayıcısını ciddi ölçüde hızlandırabilir mi, yoksa mevcut dil tasarımındaki sorunlara mı takılır?
RCU kullanım örnekleri ikna edici, ancak diğer durumlarda çöp toplama deneyimi iyi değildi
Bu yazı, statik ömrün dinamik ömürden genel olarak daha iyi olduğu iddiasından ziyade, özelleştirilmiş bellek yönetimi çözümlerinin en iyi performansı verebildiği iddiası gibi okunuyor
free()'nin belleği işletim sistemine geri verdiğine değil, ayırıcıya geri verdiğine inanıyorum. Bu, onu işletim sistemine geri vermekten çok daha iyidir ve sistem çağrıları yavaştır. Yine de mimalloc gibi, serbest bırakılan belleği bir sonrakimallociçin hemen değil yalnızca periyodik olarak kullanılabilir hâle getirenler olabilir800 bayt ayırıp hemen
freeetmeyi 1 milyon kez tekrarlayıp benzersiz işaretçi sayısını sayarsanız, glibc malloc 1, jemalloc 1, mimalloc 4, Julia çöp toplayıcısı ise 62767 verdi62767 tane, yani yaklaşık 48MiB; çok kötü değil ama yine de bilgisayarımdaki L3 önbelleğini dışarı itiyor. Çöp toplama kullandığınızda yeni ayırmaların önbellekten değil RAM'den gelmesi neredeyse garanti ve bu da ayırma yoğun kodun performansını mahvediyor. Önemli olan yalnızca bellek yönetiminin kendi hızı değil, onun verdiği bellekle ne kadar hızlı çalışabildiğiniz de
Julia'da bunu gösteren bir benchmark paylaştım: https://discourse.julialang.org/t/blog-post-rust-vs-julia-in...
malloc/free, gerçek çalışma belleği yeterince küçükse onun önbellekte sıcak kalma şansı verir. mimalloc gibi ayırıcılar da, sıkıştırmalı çöp toplama gibi, ardışık ayırmaların birbirine yakın yerleştirilmesini hedefler; benim gördüğüm mimalloc'taki 4 benzersiz işaretçi 896 bayt aralıklıydı
Sıkıştırmalı çöp toplama konusunda daha fazla deneyimim olsaydı belki daha az alaycı düşünürdüm, ancak çöp toplamanın güvenli bellek yönetimi için Rust'ın ödünç alma denetleyicisi gibi bir şeye göre çok daha karmaşık bir çözüm olduğunu düşünüyorum. Bu karmaşıklık derleyici ve çalışma zamanı geliştiricilerine yüklendiği için kullanıcılar açısından genelde sorun olmaz ve performansa duyarlı olmayan kod yazarken kabul edilebilir bir ödündür. Statik ömürlü RAII de daha özelleştirilmiş bir yaklaşım gerektirmeyen kod için makul bir ödündür ve yazıdaki örnek açıkça özelleştirilmiş çözüm gerektiren bir durum
Sıkıştırmalı çöp toplama, uzun süre çalışan programlarda yığın parçalanmasının TLB önbellek girdileri ile nesneler arasındaki boş alanı israf etmesi nedeniyle, neredeyse her zaman malloc'tan daha iyi önbellek kullanımı sağlar. Sıkıştırmalı çöp toplamanın bump allocator'ı,
freebelleği geri kazanmadığı için her ayırmada yeni bir işaretçi verir ama bu ayırmalar sıralıdır ve yığını sürekli tüketirken yalnızca en yeni nesnelere dokunulan durumlarda yine de önbellekte kalırlar. Ayırıcıların ve çöp toplayıcıların dalga etkilerini benchmark etmek son derece zordur ve bu tür sentetik benchmark'lara neredeyse her zaman şüpheyle yaklaşırımİnsanların endişelendiği şey, belleğin artık kullanılmadığını anlama sürecinin, kaynak kullanımının bittiğini ayırıcıya doğrudan söylemekten daha verimsiz ve deterministik olmaması. Serbest bırakmayı ertelemenin kendisini dert eden kimse görmedim
Tüm canlı kümenin taranması seyrektir ve 30 yıldır çöp toplama algoritmaları iyileştirilerek neredeyse fark edilmeyecek seviyeye gelindi, ancak bu ifade insanların gerçekten sorun ettiği noktayı isteyerek ya da istemeden görmezden geliyor. Serviste çöp toplama sorunu çıktığında, orayı burayı ayarlayıp öfkeli ruhu gölgeler dünyasına geri göndermeyi uman bir şaman çağırmak gerekiyormuş gibi geliyor
Çöpü işaretleyip artık kullanılmadığında bildirim alma yöntemi olsaydı, bu sürecin tamamı ortadan kalkardı. Çöp toplamada bellek ayırma çok hızlı olabilir, ancak adil karşılaştırma için işaretleme ve sıkıştırma maliyetlerini de amortize ederek dahil etmek gerekir
Bir diğer büyük sorun da, aynı performansı elde etmek için çöp toplamanın genelde elle bellek yönetiminden çok daha fazla bellek gerektirmesidir. Hâlâ referans olup olmadığını tekrar tekrar ve fazladan doğrulamak için ek CPU gerekir; ayrıca iyimser sıkıştırma için ek bellek kopyalamayı da göze almak gerekir
Son olarak yazı, ömür belirsiz olduğunda Rust'taki Arc/Rc gibi elle bellek yönetimi gerektiğini eleştiriyor, ama çöp toplayıcılı dillerde de finalizer'ın mutlaka çağrılacağına güvenilemediği için dış kaynakları kapatmak adına fiilen aynı altyapının kurulduğunu görmezden geliyor
Bu tartışma son 20~30 yılda fazlasıyla tekrarlandı ve bu yazı, çöp toplamayla ilgili meşru kaygıları meme diye geçiştirmenin dışında yeni bir şey getirmiyor gibi görünüyor. Memeler eğlenceli, o yüzden sorun değil, ama doğru cevap genel geçer tek bir doğru olmadığıdır. Sisteminizin tasarım kısıtlarını karşılamaya uygun aracı kullanmanız yeterli
Bunun dışındaki daha uzun ömürlü ayırmalar, tanım gereği önbellek açısından kontrol edilmesi kolay olmayan şeylerdir. Yerellik, çöp toplamanın büyük avantajlarından biridir ve benim bildiğim tek sorun stop-the-world işaretle-süpürdür. Modern çöp toplayıcıların arka plan iş parçacıkları kullandığını biliyorum ama yine de stop-the-world olayları yaşandığını sanıyorum
free()'nin belleği ayırıcıya geri verdiği doğru, ancak uzun süre çalışan sunucularda bellek parçalanması ile uğraşmak hiç keyifli değilÖzellikle slab allocator'ın yönettiği sayfaların iç parçalanması böyledir; yaygın bir sorun değildir ama ele alması zordur
Neyse ki çöp toplamanın böylesine avantajlı gösterildiği örneklerle rekabet etmesi gereken alan çok fazla değil
Tüm belleğin kolayca arena olarak ele alınabildiği özel durumlar dışında, iyi bir izlemeli çöp toplama yaklaşımı throughput açısından manuel bellek yönetimini çoktan geride bıraktı; son dönemde gecikmeye etkisi de uygulamaların büyük çoğunluğu için yeterince kabul edilebilir düzeyde
OpenJDK’nin ZGC’si tipik olarak iki ya da üç basamaklı mikrosaniye düzeyinde duraklama süreleri veriyor ve makul tahsis oranlarında en kötü durumda bile neredeyse hiç 1ms’i aşmıyor; bu da işletim sisteminin tetiklediği duraklamalarla benzer bir aralıkta
Gerçekten önemli olan tek ödünleşim bellek kullanımı. Arena yaklaşımının her şeye iyi uyduğu ve en kötü gecikmenin düşük mikrosaniye aralığında olduğu özel nişler dışında temel soru şu: Uygulamam bellek kısıtlı bir ortamda mı çalışıyor, ya da RAM kullanımını azaltmak için başka şeylerden ödün vermeye değer mi?
Böyle bir kodda tek tek nesnelerin yaşam süresini izlemek aşırıya kaçıyor. Sonuçta bellek yönetimi yaşam süreleriyle ilgilidir ve çok sayıda ayrı yaşam süresi yerine daha az sayıda ayrı yaşam süresi her zaman daha iyidir. Çünkü ister manuel ister otomatik olsun, yapılacak iş azalır
Nesne yaşam sürelerini düşünmek zorunda olmamak son derece kullanışlıdır; iyi bir çöp toplayıcının iç karmaşıklığı epey yüksek olsa da çöp toplamalı dillerin başarılı olmasının nedeni de budur
Öngörmesi zor kuyruk gecikmeleri ve çeşitli istisnai durumların etkisi de sürüyor
ZGC, Shenandoah’a göre daha az sayıda duraklama yapıyor gibi görünüyor; bu yüzden her duraklamada daha fazla iş yapıp biraz daha iyi performans veriyor
Hâlâ üretim ortamı testleri yapmamız gerekiyor ama şu ana kadar, ZGC ve özellikle Java 21 sonrası nesilsel ZGC kullanıldığında çöp toplama duraklamaları büyük ölçüde çözülmüş bir sorun gibi görünüyor
Jai perspektifindeki gibi, bellek tahsisini yaygınlık sırasına göre dört kategoriye ayırabiliriz: 1) son derece kısa ömürlü olup fonksiyon stack’ine konabilenler, 2) kısa ömürlü olup yaşam süresi iyi tanımlandığı için frame/istek düzeyinde bellek arenasına konabilenler, 3) uzun ömürlü olup sahibi iyi tanımlandığı için alt sistemlere özel havuzlarla yönetilebilenler, 4) uzun ömürlü olup sahibi belirsiz olduğu için dinamik bellek yönetimi gerektirenler
İzlemeli çöp toplamanın genel olarak manuel bellek yönetiminden üstün olduğunu savunacaksak, bunu sağda solda malloc/free çağıran bir sistemle değil, bu bakış açısı gözetilerek yazılmış sistemlerle karşılaştırmak gerekir. Modern C++/Rust pratikleriyle karşılaştırmak daha adil olabilir
Yine de çoğu sistemde izlemeli çöp toplamaya dayanmanın çok daha pratik olacağına katılıyorum; ama bu tamamen farklı bir iddia
Yazı, RCU için motivasyon oluşturduktan sonra aniden direksiyonu kırıp genel amaçlı çöp toplamayı genel olarak savunmaya başlıyor
Truva atı kadar olmasa da oldukça sert bir yön değişikliği gibi hissettiriyor
Nesneler üç durumdan birinde bulunuyor ve mümkün olduğunca hızlı geçiş yapıyor: active, obsolete but alive for old readers, deallocated
Kodun nasıl yazıldığına bağlı olarak, “obsolete-but-alive” nesneleri güvenli biçimde yeni
newtahsisleri için yeniden kullanmak mümkün olabilir; ancak performansı tam olarak analiz etmiş değilimÇöp toplama tartışmalarında sık görüldüğü gibi, ne zaman
shared_ptr/Arckullanımına “geri dönmek” gerektiği çok muğlak. Pratikte referans sayımından kaçınmak, yani zaten sahiplik olduğunu kanıtlamak ya da dolaylı başvurudan tamamen kaçınmak, ciddi bir referans sayımı tabanlı sistemin özüdür. Hiçbir şey yapmamak, çöp toplamanın “bir gün bir şey yapması”ndan açıkça daha iyidirBenim kullandığım yazılımlarda iki durum var. (1) her zaman özel tahsis edicilerin kullanıldığı ve tahsisten kaçınılacak sıcak yollar, (2) bunun dışındaki her şey
(1) için çöp toplama olsun ya da olmasın fark etmez; ben zaten bunun dışına çıkarım. (2) için ise çöp toplama gerçekten kullanışlı ve doğru
Java’nın modern çöp toplayıcılarla yaptığı işler etkileyici ama onların bile Valhalla ile dolaylı olarak tahsissiz/düşük tahsisli kodun bir yeri olduğunu kabul ettiğini görüyoruz
Modern kullanıcı işletim sistemlerinde, yani özel RTOS olmayan işletim sistemlerinde yerleşik bir çöp toplayıcı bulunduğu gözlemi burada biraz ıskalanıyor
Biz buna sadece böyle demiyoruz, bellek yönetimi diyoruz. Yerleşik çöp toplayıcısı olan dillere ne denir? Bellek yönetimli diller denir
Eski tip “yukarıdan aşağıya” çalışan C programlarında bunu sık görürsünüz. Tahsis eder, sistem kaynaklarını temizlersiniz ama
freeile pek uğraşmazsınız. Program bittiğinde işletim sistemi o belleğin tamamını geri alacaksa neden uğraşasınız kiBurada, programla daha az yalıtılmış, işletim sistemi düzeyinde bir çöp toplayıcı ya da dil çalışma zamanı çöp toplayıcısı gibi kaynakları ele alan bir işletim sistemi yapma fırsatı var. Ama genelde çöp toplamalı dillerde çöp toplayıcı çalışma zamanının neredeyse her satırına karmaşık şekilde dolanmıştır; bu yüzden yalnızca tek bir işletim sistemi dağıtımında denetimi işletim sistemine devredecek bir yapı kurmak pratik değildir
Yine de yazık. Çünkü program düzeyi bellek yönetimi ile işletim sistemi düzeyi bellek yönetimi arasındaki yapay ayrımdan doğan kronik sorunları iyileştirmek için çok yer var
Belleği serbest bırakmanın tek nedeni, uzun ömürlü uygulamalarda işletim sisteminden yeni bellek almadan onu başka tahsisler için yeniden kullanmaktır. Bir kez çalışıp biten komut satırı araçlarında buna genelde gerek yoktur
Süreç işini bitirince çıkacak şekilde tasarlanmışsa, işletim sistemini bir çöp toplayıcı gibi kullanabilirsiniz
Ama çalışan bir programın içinde hangi belleğin artık kullanılmadığını işletim sisteminin bilmesinin bir yolu hiçbir zaman olmadı. Muğlak araştırma amaçlı işletim sistemleri istisna olabilir. Bu yüzden bunu kaçırılmış bir fırsattan çok, kaçırıldığı söylenen şeyin anlamlı bir biçimde hiç var olmaması olarak görüyorum
Öte yandan, çok basit ve kısa ömürlü programlar yazma tarzı tamamen meşrudur. CLI araçları ve onları betikleyen betik dilleri böyle çalışır, eski web sunucuları da CGI vb. ile böyle çalışıyordu ve bugün bile gayet makul bir yaklaşımdır
(1) RCU’dan genel amaçlı izlemeli çöp toplamaya geçiş, yem atıp sonra konuyu değiştirmek gibi geliyor
(2) Elle bellek yönetimi sadece malloc/free çağrıları değil, yerleşimle de ilgilidir. Örneğin yapı dizilerinin ayrılması, inline yerleştirme, örtük ofsetler, packing gibi şeyler vardır
Sanırım kastedilen “structure of arrays”; bu, tuple dizileriyle yapılabilir ve hedefe bağlı olarak doğal biçimde düzleştirilip normalleştirilir. Yani native hedeflerde bu, yapı dizisine dönüşür
Bayt düzeyinde tam yerleşim de tanımlanabilir[1]; bu daha çok başka yazılımlarla birlikte çalışabilirlik veya ikili biçim ayrıştırma için kullanılır. Cebirsel veri tipleri unbox edilebilir ve yakında cebirsel veri tiplerinin tam kodlaması bile kontrol edilebilir olacak
Virgil çöp toplama kullanır
[1] https://github.com/titzer/virgil/blob/master/doc/tutorial/La...
Modern yönetimli dillerde bellek yerleşimi kontrolünün yetersiz olduğu doğru, ama düşük seviye dillerde de durum mükemmel olmaktan uzak ve etki etmenin yolları kesinlikle var
Bu yazıda eksik kalan bir şey de async/await’in çöp toplamayla çok iyi uyuşması
Kişisel ve biraz tuhaf üslup nedenleriyle async/await’ten hoşlanmıyorum ama uzatmayacağım
Bunu TypeScript/JavaScript’te çok kullandım, Dart’ta da kullandım ve orada beklendiği gibi çalışıyor
Rust’ta da kullandım ama bana göre felaket. Çok iş parçacıklı bir çalışma zamanında async/await kullanmak için gereken türden bellek yönetimini zorla araya sıkıştırmak tam bir cehennem
https://doc.rust-lang.org/std/pin/index.html
Çöp toplamayı savunan birçok yazıda gözden kaçan bir nokta var; bu yazıda da öyle görünüyor: bellek sadece bir kaynak türü
Özellikle sistem programlamada doğru kodun dosya tanıtıcıları, soketler gibi harici kaynakları da yönetmesi gerekir. Çöp toplama yalnızca uygulamanın bellek kısmını çözdüğü için bu tür harici kaynakların işlenmesinde hiç yardımcı olmaz. Hatta işleri çok daha karmaşık hale getirebilir; .NET'te hiç de önemsiz olmayan bir
IDisposableuygulamasını doğru yazmak için neler gerektiğine bakmak bile bunu gösterirDeneyimime göre RAII veya referans sayımı gibi yaklaşımlar, bellek ve harici kaynakları birleşik bir şekilde ele almayı çok daha kolaylaştırıyor; böylece doğru kod yazmak ve bunun üzerine akıl yürütmek de kolaylaşıyor
Bu, çöp toplamaya açıkça karşı olduğum anlamına gelmiyor. Diğer her şey gibi artıları ve eksileri olan bir araç. Yazıda bahsedilen “manuel GC” olan RCU yaklaşımı, belirli işler için ilginç
Bellek ile diğer kaynaklar arasında büyük bir fark var. Bellek, işlem gücü gibi tüm hesaplamaların temel unsurudur
Çoğu kuramsal hesaplama modelinin sonsuz bellek varsayması boşuna değil. İşletim sistemi çekirdeği ya da katı gerçek zamanlı uygulamalar gibi bazı yazılımlarda işlem gücü de elle tahsis edilir, ancak işlem gücünün elle tahsis edilmesini gerektiren diller neredeyse yoktur
Benzer nedenlerle otomatik bellek yönetimi, hesaplamayı soyutlamak için çok faydalıdır. Bir alt yordamın bellek ayrıntılarının çağırana sızmamasını sağlar; CPU kullanımında da bu tür ayrıntılar nadiren görünür
Önemsiz olmayan her hesaplama, sabit olmayan bir miktarda işlem ve bellek içerir; ancak G/Ç genellikle sistemin sınırlarında gerçekleşir. G/Ç yönetimi elbette çok önemlidir, ancak hesaplama kavramı ve hesaplama soyutlamasının merkeziliği açısından işlem ve bellek kadar temel değildir
Doğru; bellek güvenliği argümanı diğer kaynaklara da uygulanır
Örneğin Rust sonunda G/Ç güvenliğine de kavuştu; bu yüzden Unix'teki
OwnedFdgibi dosya tanıtıcıları veya Windows'takiOwnedHandlegibi tanıtıcılar, 4 sayısı gibi bir tamsayı değil, sahip olunan nesneler haline geliyorYüzeyde bu, tanıtıcılar üzerinde aritmetik yapmak ya da ayrılmış bir değeri yanlışlıkla sentinel olarak kullanmak gibi aptalca hataları önleme meselesi gibi görünebilir; ancak sahiplik modeli sayesinde, tanıtıcılarla karmaşık işler yapılsa bile açık sahiplik oluşur ve bu da sonradan bakım yapacak kişiler için şeffaf olur
Ağırlıklı olarak C++'tan C#'a geçince, çöp toplamalı bellek yönetimini beğeniyorum ama dosya tanıtıcıları, soketler vb. şeyleri takip etmekten hoşlanmıyorum
RAII'nin değerini gerçekten çok hissettim
Çöp toplamalı dilleri eleştirirken sıkça gözden kaçan nokta şu: Bunların çoğu, belirlenimli kaynak yönetimi için özelliklere sahip, ama pek çok kişi bunları öğrenmiyor
Bazı dillerde RAII vardır, bazıları anahtar sözcük sağlar, bazıları ise arena benzeri yönetim ya da örtük yönetime sahip lambda'lar sunar. Bazı diller tür sisteminden biraz yardım alır, bazıları ise yukarıdakileri biraz biraz birleştirir
Ayrıca sistem geliştiricilerinin statik çözümleyicilere güvenmek zorunda kalması gibi, bu dillerin statik çözümleyicileri de tür sisteminin tek başına yeterli olmadığı yerlerde gözden kaçanları doğrulayabilir
RAII elbette harika, ancak çöp toplama ve düzgün istisna işleme olan bir dilde kaynaklar güvenli biçimde ele alınabilir
Örneğin Java'daki try-with-resources ifadesi, istisna oluşsa bile kaynakların güvenli şekilde serbest bırakılacağını garanti eder: https://docs.oracle.com/javase/tutorial/essential/exceptions...
Bu temel yapı taşlarıyla da oldukça sağlam ve kaynak açısından güvenli sistemler kurulabilir