17 Zayıf Tablolar

17 Zayıf Tablolar


Lua otomatik bellek yönetimi uygular. Bir program yalnızca nesneleri oluşturur (tablolar, fonksiyonlar vb.); nesneleri silmek için bir fonksiyon yoktur. Lua, çöp toplayıcı kullanarak çöp haline gelen nesneleri siler. Bu sizi bellek yönetimi yükünün çoğundan kurtarır ve daha da önemlisi bu etkinlikle ilgili hataların çoğundan kurtarır; sarkan işaretçiler ve bellek sızıntıları gibi.

Diğer bazı toplayıcılardan farklı olarak Lua'nın çöp toplayıcısının girift verilerle ilgili herhangi bir problemi yoktur . Girift veri yapıları kullanırken herhangi bir özel işlem yapmanız gerekmez; Diğer veriler gibi toplanırlar. Yine de bazen bu akıllı toplayıcının yardımınıza ihtiyacı olur. Hiçbir çöp toplayıcı bellek yönetimi hakkında tüm endişeleri gidermez.

Çöp toplayıcı sadece emin olduğu çöpleri toplayabilir; çöp olarak değerlendirdiklerinizi bilemez. Tipik bir örnek Lua 'da böyle olmayan indeksin üstte olduğu bir dizi uygulaması olan yığındır(stack). Dizinin geçerli kısmının sadece en üst olduğunu bilirsiniz ancak Lua bilmez. Eğer bir öğeyi üstten alıp çıkarırsanız dizide kalan Lua için çöp değildir. Yine, global bir değişkende depolanan herhangi bir nesne Lua için çöp değildir olsa programınız bir daha asla kullanamaz. Her iki durumda da bu pozisyonlara nil atamak size kalmış (yani programınıza ) , böylece nesneler  çakılı kalmaz serbest bırakılır.

Ancak sadece başvuruları temizlik her zaman yeterli değildir. Bazı yapılar, program ve toplayıcı arasında ekstra işbirliğini gerektirir. Programınızda bazı nesne türlerinin tüm canlı  koleksiyonunu  (örneğin dosyalar) tutmak istemeniz bunun tipik bir örneğidir. Bu görev basit görünüyor: tek yapmanız gereken her yeni nesneyi koleksiyona eklemek. Ancak nesne koleksiyonun içinde olduğunda asla toplanmayacak! Hiçbir başvuru olmasa bile koleksiyonun vardır. Lua, Lua'a bu gerçeği anlatmadığınız sürece bu başvurunun nesnenin ıslah edilmesini engellememesi gerektiğini bilemez.

Zayıf tablolar Lua'a bir başvurunun bir nesnenin ıslah edilmesini engellememesi gerektiğini söylemek için kullandığımız mekanizmadır. Zayıf başvuru, çöp toplayıcısı tarafından dikkate alınmayan bir nesneye başvurudur. Bir nesneye işaret eden tüm başvurular zayıfsa nesne toplanır ve bir şekilde bu zayıf başvurular silinir. Lua zayıf tablolar olarak zayıf başvuruları uygular: Zayıf tablo, girdileri zayıf olan bir tablodur. Bu, bir nesne yalnızca zayıf tablolar içinde tutulursa Lua nihayetinde nesneyi toplayacak demektir.

Tablolar, anahtarlar ve değerlere sahiptir ve her ikisi de  herhangi türde nesne içerebilir. Normal koşullar altında çöp toplayıcı,  erişilebilir tablonun değerleri veya anahtarları olarak görünen nesneleri toplamaz. Yani hem anahtarlar hem de değerler başvuru oldukları nesnelerin ıslah edilmesini önledikleri için güçlü referanslardır. Zayıf tablo içinde anahtarlar ve değerler zayıf olabilir. Bu, üç çeşit zayıf tablo olduğu anlamına gelir: zayıf anahtarlara sahip tablolar, zayıf değerlere sahip tablolar ve hem anahtarların hem de değerlerin zayıf olduğu tamamen zayıf tablolar. Tablo türüne bakılmaksızın bir anahtar veya bir değer toplandığında tüm girişler tablodan kaybolur.

Bir tabloya zayıflığı metatablosunun __mode alanı ile verilir. Bu alanın değeri mevcut olduğunda bir string olmalıdır: bu string 'k' harfini içeriyorsa tablodaki anahtarlar zayıftır; bu string 'v' harfini içeriyorsa tablodaki değerler zayıftır. Aşağıdaki örnek yapay olmasına rağmen zayıf tabloların temel davranışını göstermektedir:

a = {}
b = {__mode = "k"}
setmetatable(a, b)  -- artık 'a' zayıf anahtarlara sahip
anahtar = {}            -- ilk anahtar yaratılıyor
a[anahtar] = 1
anahtar = {}            -- ikinci anahtar yaratılıyor
a[anahtar] = 2
collectgarbage()    -- çöp toplama döngüsüne zorla
for k, v in pairs(a) do print(v) end
--> 2

Bu örnekte ikinci atama anahtar ={}, ilk anahtar üzerine yazıyor. Toplayıcı çalıştığında ilk anahtara başka bir başvuru yok bu nedenle toplanır ve tablodaki karşılık gelen giriş kaldırılır. Bununla birlikte ikinci anahtar hala anahtar değişkeninde demirlenik bu nedenle toplanmaz.

Zayıf tablodan yalnızca nesnelerin toplanabileceğine dikkat.  sayılar ve boolean'lar gibi değerler toplanabilir değildir. Örneğin tablo a'ya(önceki örneğimizden) sayısal bir  anahtar eklersek toplayıcı tarafından asla kaldırılmayacak. Tabii ki sayısal anahtara karşılık gelen değer toplanırsa o zaman tüm girişler zayıf tablodan kaldırılır.

Stringler burada bir incelik sunar: stringler toplanabilir olmasına rağmen uygulama bakış açısından diğer toplanabilir nesneler gibi değildirler. Tablolar ve fonksiyonlar gibi diğer nesneler açıkça oluşturulur. Örneğin Lua, {} ifadesini değerlendirdiğinde yeni bir tablo oluşturur. function()...end 'i değerlendirdiğinde yeni bir fonksiyon oluşturur (aslında bir closure). Ancak, "a".."b" 'i değerlendirdiğinde Lua yeni bir string oluşturur mu? Sistemde zaten bir "ab" stringi varsa ne olur? Lua yeni bir tane yaratır mı? Derleyici programı çalıştırmadan önce bu stringi oluşturabilir mi? Önemli değil: bunlar uygulama ayrıntılarıdır. Programcının bakış açısından stringler nesne değil, değerdirler. Bu nedenle bir sayı veya bir boolean gibi, bir string'de zayıf tablolardan kaldırılmaz (ilişkili değeri toplanmadıkça).

17.1 Ezber(Memoize) Fonksiyonlar

Yaygın bir programlama tekniği, space–time veya time–memory trade-off'dır(hesaplama süresi veya cevap süresini kısaltma). Sonuçlarını ezberleyerek bazı fonksiyonları hızlandırabiliriz bu şekilde daha sonra fonksiyonu aynı argümanla çağırdığınızda sonucu yeniden kullanabilir.

Lua kodlu stringler içeren istekler alan genel bir sunucu düşünün. Her seferinde bir istek aldığında string üzerinden loadstring 'i çalıştırır ve sonra ortaya çıkan fonksiyonu çağırır. Ancak loadstring masraflı bir fonksiyondur ve sunucuya bazı komutlar oldukça sık olabilir. "baglantiyikapa()" gibi yaygın bir komutu her aldığında tekrar tekrar loadstring 'i çağırmak yerine sunucu, yardımcı bir tablo kullanarak loadstring 'den sonuçları ezberleyebilir. loadstring çağrılmadan önce sunucu, verilen stringin halihazırda bir çevrime sahip olup olmadığını tabloda kontrol eder. string'i bulamazsa o zaman sunucu loadstring'i çağırır ve sonucu tabloya depolar. Bu davranışı yeni bir fonksiyonda paketleyebiliriz:

local sonuclar = {}
function mem_loadstring(s)
    local sonuc = sonuclar[s]
    if sonuc == nil then              -- sonuç mevcut değil mi?
        sonuc = assert(loadstring(s)) -- yeni sonuç hesapla
        sonuclar[s] = sonuc            -- daha sonraki yeniden kullanım için kaydet
    end
    return sonuc
end

Bu düzenle tasarruf devasa olabilir. Bununla birlikte korunmasız atıklara da neden olabilir. Bazı komutlar üst üste tekrarlansa da diğer birçok komut sadece bir kez gerçekleşir. Yavaş yavaş, sonuclar tablosu sunucunun aldığı tüm komutlar artı ilgili kodlarını biriktirir; Belli süre sonra bu davranış sunucunun belleğini tüketir. Zayıf tablo bu soruna basit bir çözüm sağlar. sonuclar tablosu zayıf değerlere sahipse her çöp toplama döngüsü o anda kullanılmayan tüm çevrimleri kaldıracaktır (bu da hemen hemen neredeyse hepsi anlamına gelir):

local sonuclar = {}
setmetatable(sonuclar, {__mode = "v"}) -- değerleri zayıf yap
function mem_loadstring (s)
 <önceki gibi>

Aslında indeksler her zaman stringler olduğu için eğer istersek bu tabloyu tamamen zayıf yapabiliriz:

setmetatable(sonuclar, {__mode = "kv"})

Net sonuç aynı.

Ezber tekniği aynı zamanda bir nesne türünün benzersizliğini sağlamak için de yararlıdır. Örneğin, belli erminde kirmizi, yesil ve mavi alanlı tablolarla renkleri temsil eden bir sistem düşünelim. Temel düzeyli renk fabrikası her yeni istek için yeni bir renk üretir:

function uretRGB(r, g, b)
    return {kirmizi = r, yesil = g, mavi = b}
end

Ezber tekniğini kullanarak aynı renk için aynı tabloyu yeniden kullanabiliriz. Her renk için benzersiz bir anahtar oluşturmak için basitçe aralarında bir ayırıcı ile renk indekslerini birleştiririz:

local  sonuclar = {}
setmetatable(sonuclar, {__mode = "v"}) -- değerleri zayıf yap
function uretRGB(r, g, b)
    local anahtar = r .. "-" .. g .. "-" .. b
    local renk = sonuclar[anahtar]
    if renk == nil then
        renk = {kirmizi = r, yasil = g, mavi = b}
        sonuclar[anahtar] = renk
    end
    return renk
end

Bu uygulamanın ilginç bir sonucu, kullanıcının basit = operatörünü kullanarak renkleri karşılaştırabilmesidir çünkü iki eş renk her zaman aynı tablo ile temsil edilir. Aynı rengin farklı zamanlarda farklı tablolarla temsil edilebileceğini unutmayın çünkü zaman zaman çöp toplayıcı döngüsü sonuclar tablosunu temizler. Bununla birlikte, verilen bir renk kullanımda olduğu sürece sonuclar 'dan kaldırmaz. Böylece, bir renk yenisiyle karşılaştırmaya yetecek kadar hayatta kaldığı sürece vekili de yeni renk ile tekrar kullanabilecek kadar hayatta kalır.

17.2 Nesne Nitelikleri

Zayıf tabloların bir diğer önemli kullanımı, nesnelerle niteliklerini ilişkilendirmektir. Bir nesneye bazı nitelikler eklememiz gereken sayısız durum vardır: fonksiyonlara isimler, tablolara varsayılan değerler, dizilere boyutlar vb.

Nesne bir tablo olduğu zaman niteliği tablonun kendisinde uygun benzersiz bir anahtarla saklayabiliriz. Daha önce gördüğümüz üzere benzersiz bir anahtar oluşturmak için basit ve hataya dayanıklı bir yol, yeni bir nesne (genellikle bir tablo) oluşturmak ve onu anahtar olarak kullanmaktır. Ancak nesne bir tablo değilse kendi niteliklerini tutamaz. Tablolar için bile bazen niteliği orijinal nesnede saklamak istemeyebiliriz. Örneğin niteliği özel tutmak isteyebiliriz veya niteliğin bir tabloda dolaşırken rahatsızlık vermesini istemeyiz. Tüm bu durumlarda, nitelikleri nesnelere ilişkilendirmenin alternatif bir yoluna ihtiyacımız var. Tabii ki, harici bir tablo nesnelere nitelikleri ilişkilendirmek için ideal bir yol sağlar (bu tablolara bazen ilişkisel(associative) diziler denmesi tesadüf değil).  Anahtarlar olarak nesneleri ve değerler olarak niteliklerini kullanıyoruz. Lua, anahtar olarak herhangi bir nesne türünü  kullanmamıza izin verdiği için harici bir tablo herhangi bir nesne türünün niteliklerini tutabilir. Dahası, harici bir tabloda tutulan nitelikler diğer nesnelere salça olmaz ve kendi tablosu olarak özel olabilir.

Bununla birlikte, bu görünüşte mükemmel çözümün büyük bir dezavantajı var: bir nesneyi bir tablodaki bir anahtar olarak kullandığımızda, kalıcı olarak nesneyi kilitleriz. Lua , bir anahtar olarak kullanılan bir nesneyi toplayamaz. Fonksiyonlara isimlerini ilişkilendirmek için normal bir tablo kullanırsak bu fonksiyonların hiçbiri toplanmayacaktır. Beklediğiniz gibi, zayıf tablo kullanarak bu dezavantajdan kaçınabiliriz. Ancak bu sefer, zayıf anahtarlara ihtiyacımız var. Zayıf anahtarların kullanımı, başka bir başvuru olmadığında, herhangi bir anahtarın toplanması engellemesini yaptırtmaz. Öte yandan, tablo zayıf değerlere sahip olamaz; aksi halde canlı nesnelerin nitelikleri toplanabilir.


17.3 Varsayılan değerli tabloları yeniden ziyaret

Bölüm 13.4'te nil olmayan varsayılan değerli tabloları nasıl uygulayacağımızı ele aldık. Belirli bir tekniği gördük ve diğer iki tekniğin zayıf tablolara ihtiyaç duyduğunu söyledik bu yüzden onları erteledik. Şimdi konuyu tekrar ziyaret zamanı. Göreceğimiz üzere, varsayılan değerler için bu iki teknik aslında burada gördüğümüz iki genel tekniğin belirli uygulamalarıdır: nesne nitelikleri ve ezber.

İlk çözümde, her tabloya varsayılan değerini ilişkilendirmek için zayıf tablo kullanıyoruz:

local varsayilanlar = {}
setmetatable(varsayilanlar, {__mode = "k"})
local mt = {__index = function(t) return varsayilanlar[t] end}
function setVarsayilanlar(t, d)
    varsayilanlar[t] = d
    setmetatable(t, mt)
end

Eğer varsayilanlar zayıf anahtarlara sahip değilse varsayılan değerli tüm tabloları kalıcı varlık olarak kitler.

İkinci çözümde, farklı varsayılan değerler için farklı metatablolar kullanıyoruz ancak varsayılan bir değeri tekrarladığımızda aynı metatabloyu yeniden kullanıyoruz. Bu ezberin tipik bir kullanımıdır:

local metalar = {}
setmetatable(metalar, {__mode = "v"})
function setVarsayilanlar(t, d)
    local mt = metalar[d]
    if mt == nil then 
        mt = {__index = function() return d end}
        metalar[d] = mt -- ezber
    end
    setmetatable(t, mt)
end

Bu durumda, artık kullanılmayan metatabloların toplanmasına izin vermek için zayıf değerler kullanıyoruz.

Varsayılan değerler için bu iki uygulama göz önüne alındığında hangisi en iyisi? Her zamanki gibi değişir. Her ikisi de benzer karmaşıklığa ve benzer performansa sahiptir. İlk uygulama, varsayılan değer (varsayilanlar 'larda girdi) ile her tablo için birkaç kelime hafızası ihtiyaç duyar. İkinci uygulama, her ayrı varsayılan değer için birkaç düzine kelime hafızası ihtiyaç duyar(yeni bir tablo, yeni bir closure ve metalar'a bir girdi). Yani, uygulamanızın birkaç farklı varsayılan değere sahip binlerce tablo varsa ikinci uygulama açıkça üstündür. Öte yandan, birkaç tablo ortak varsayılanları paylaşıyorsa ilk uygulamayı tercih etmelisiniz.

Hiç yorum yok:

Yorum Gönder