Jump to content

Çalışma Zamanı Bellek Organizasyonu


Doğuhan ELMA

224 views

Linux veya Windows gibi bir işletim sistemi, ana belleğin farklı alanlarına (bölümler veya bölümler) farklı türde veriler yerleştirir. Bir bağlayıcı çalıştırarak ve çeşitli komut satırı parametreleri belirleyerek bellek organizasyonunu kontrol etmek mümkün olsa da, varsayılan olarak Windows, Şekil 1'de görünen organizasyonu kullanarak belleğe tipik bir program yükler. Linux, bazılarını yeniden düzenlemesine rağmen benzerdir.

bellek_01.jpg Şekil-1: Bir belleğin organizasyon yapısı

 

İşletim sistemi en düşük bellek adreslerini ayırır. Genel olarak, uygulamanız bellekteki en düşük adreslerdeki verilere erişemez (veya talimatları yürütemez). İşletim sisteminin bu alanı ayırmasının bir nedeni, NULL işaretçi referanslarının algılanmasına yardımcı olmaktır. Programcılar, işaretçinin geçerli olmadığını belirtmek için genellikle işaretçileri NULL (sıfır) ile başlatır. Böyle bir işletim sistemi altında sıfır bellek konumuna erişmeye çalışırsanız, işletim sistemi geçersiz bir bellek konumuna eriştiğinizi belirtmek için bir genel koruma hatası oluşturur.

 

Hafıza haritasında kalan yedi alan, programınızla ilişkili farklı türde verileri tutar. Bu bellek bölümleri şunları içerir:

1.       Programın makine komutlarının tutulduğu kod bölümü(Code).

2.       Derleyici tarafından oluşturulan salt okunur verileri tutan sabit bölüm(Constant).

3.       Yalnızca okunabilen, asla yazılamayan kullanıcı tanımlı verileri tutan salt okunur veri bölümü(Read-only data).

4.       Kullanıcı tanımlı, başlatılmış statik(static variables) değişkenleri tutan statik bölüm.

5.       Kullanıcı tanımlı başlatılmamış değişkenleri tutan depolama(storage) bölümü veya BSS bölümü.

6.       Programın yerel değişkenleri ve diğer geçici verileri tuttuğu yığın(Stack) bölümü.

7.       Programın dinamik değişkenleri tuttuğu yığın(heap) bölümü.

Çoğu zaman, bir derleyici kod, sabit ve salt okunur veri bölümlerini birleştirir çünkü üç bölümün tümü salt okunur veriler içerir. Çoğu zaman, belirli bir uygulama, derleyici ve bağlayıcı tarafından bu bölümler için seçilen varsayılan düzenlerle yaşayabilir.

 

1.       Statik ve Dinamik Nesneler(Objects), Bağlama(Binding) ve Yaşam Süresi(Lifetime):

Tipik bir programın hafıza organizasyonunu anlamak için, önce tartışmamızda faydalı olacak birkaç terim tanımlamalıyız. Bu terimler bağlayıcı, yaşam süresi, statik ve dinamiktir.

Bağlama(Binding), bir özniteliği(attribute) bir nesneyle ilişkilendirme işlemidir. Örneğin bir değişkene değer atadığınızda, atama noktasında değerin o değişkene bağlı olduğunu söylüyoruz. Değer, siz ona başka bir değer bağlayana kadar (başka bir atama işlemi aracılığıyla) değişkene bağlı kalır. Aynı şekilde program çalışırken bir değişkene bellek ayırırsanız değişken o noktada adrese bağlanmıştır deriz. Siz değişkenle farklı bir adres ilişkilendirene kadar değişken ve adres birbirine bağlıdır. Bağlamanın çalışma zamanında gerçekleşmesi gerekmez. Örneğin, derleme sırasında değerler sabit nesnelere bağlanır ve bu tür bağlamalar program çalışırken değiştirilemez.

Bir özniteliğin(attribute) ömrü, o özniteliği bir nesneye ilk kez bağladığınız noktadan, belki de nesneye farklı bir özniteliği bağlayarak bu bağı kopardığınız noktaya kadar uzanır. Örneğin, bir değişkenin ömrü, belleği değişkenle ilk ilişkilendirdiğiniz andan o değişkenin deposunu serbest bıraktığınız ana kadardır.

Statik nesneler, uygulamanın yürütülmesinden önce kendilerine bağlı bir özniteliği olan nesnelerdir. Sabitler(constants), statik nesnelerin iyi örnekleridir; uygulamanın yürütülmesi boyunca kendilerine bağlı aynı değere sahiptirler. Pascal, Python gibi programlama dillerindeki genel (program düzeyinde) değişkenler(variable), programın ömrü boyunca kendilerine bağlı aynı adrese sahip oldukları için statik nesnelere örnektir. Bu nedenle, statik bir nesnenin ömrü, programın ilk kez yürütülmeye başladığı noktadan uygulamanın sona erdiği noktaya kadar uzanır. Sistem, program yürütülmeye başlamadan önce öznitelikleri statik bir nesneye bağlar (genellikle derleme sırasında veya bağlama aşamasında, ancak değerleri daha erken bağlamak da mümkündür).

Tanımlayıcı kapsamı kavramı, statik bağlama ile de ilişkilidir. Bir tanımlayıcının kapsamı, programın, tanımlayıcı adının nesneye bağlı olduğu bölümüdür. Adlar yalnızca derleme sırasında var olduğundan, kapsam derlenmiş dillerde kesinlikle statik bir özniteliktir. (Yorumlayıcının program yürütme sırasında tanımlayıcı adlarını tuttuğu yorumlayıcı dillerde, kapsam statik olmayan bir öznitelik olabilir.) Yerel bir değişkenin kapsamı genellikle, onu bildirdiğiniz prosedür veya işlevle (veya herhangi bir iç içe geçmiş prosedür veya Pascal gibi blok yapılı dillerdeki işlev bildirimleri) ve ad, alt programın dışında görünmez. Aslında, bir tanımlayıcının adını farklı bir kapsamda (yani, farklı bir işlev veya prosedürde) yeniden kullanmak mümkündür. Böyle bir durumda tanımlayıcının ikinci kullanımı, tanımlayıcının ilk kullanımından farklı bir nesneye bağlanacaktır.

Dinamik nesneler, program çalışırken kendilerine bazı özniteliklerin atandığı nesnelerdir. Program çalışırken bu özelliği (dinamik olarak) değiştirmeyi seçebilir. Bu özniteliğin ömrü, uygulama özniteliği nesneyle ilişkilendirdiğinde başlar ve program bu ilişkilendirmeyi bozduğunda sona erer. Program bir özniteliği bir nesneyle ilişkilendirir ve ardından bu bağı asla bozmazsa, özniteliğin ömrü, ilişkilendirme noktasından programın sona erdiği noktaya kadardır. Uygulama yürütmeye başladıktan sonra, sistem dinamik nitelikleri çalışma zamanında bir nesneye bağlar.

Bir nesnenin statik ve dinamik niteliklerin bir kombinasyonuna sahip olabileceğini unutmayın. Örneğin, statik bir değişken, programın tüm yürütme süresi boyunca kendisine bağlı bir adrese sahip olacaktır. Ancak, aynı değişken, programın ömrü boyunca kendisine bağlı farklı değerlere sahip olabilir. Bununla birlikte, herhangi bir öznitelik için, öznitelik ya statik ya da dinamiktir; ikisi birden olamaz.

 

2.       Kod, Salt Okunur ve Sabit Bölümler:

Bellekteki kod bölümü, bir program için makine komutlarını içerir. Derleyiciniz, yazdığınız her ifadeyi bir veya daha fazla bayt değeri dizisine çevirir. CPU, program yürütme sırasında bu bayt değerlerini makine yönergeleri olarak yorumlar.

Çoğu derleyici ayrıca bir programın salt okunur verilerini kod bölümüne ekler çünkü kod yönergeleri gibi salt okunur veriler zaten yazmaya karşı korumalıdır. Ancak, yürütülebilir dosyada ayrı bir bölüm oluşturmak ve onu salt okunur olarak işaretlemek Windows, Linux ve diğer birçok işletim sistemi altında tamamen mümkündür. Sonuç olarak, bazı derleyiciler ayrı bir salt okunur veri bölümünü destekler. Bu tür bölümler, programın yürütülmesi sırasında programın değiştirmemesi gereken, başlatılmış verileri, tabloları ve diğer nesneleri içerir.

Şekil 1'de bulunan sabit bölüm tipik olarak derleyicinin ürettiği verileri içerir (kullanıcı tanımlı salt okunur verileri içeren salt okunur bölümün aksine). Çoğu derleyici aslında bu verileri doğrudan kod bölümüne yayar. Bu nedenle çoğu yürütülebilir dosyada kod, salt okunur veri ve sabit veri bölümlerini birleştiren tek bir bölüm bulacaksınız.

 

3.       Statik Değişkenler Bölümü:

Pek çok dil, derleme aşamasında genel bir değişken başlatma yeteneği sağlar. Örneğin, C/C++'da bu statik nesneler için başlangıç değerleri sağlamak üzere aşağıdaki gibi ifadeler kullanabilirsiniz:

 

static int i = 17;  static char ch[] = ( 'a', 'b', 'c', 'd' };

 

C/C++ ve diğer dillerde, derleyici bu ilk değerleri çalıştırılabilir dosyaya yerleştirecektir. Uygulamayı çalıştırdığınızda, işletim sistemi yürütülebilir dosyanın bu statik değişkenleri içeren kısmını belleğe yükleyecek ve böylece değerler kendileriyle ilişkili adreslerde görünecektir. Bu nedenle, program ilk kez yürütülmeye başladığında, bu statik değişkenler sihirli bir şekilde bu değerleri kendilerine bağlayacaktır.

 

4.       Başlatılmamış Depolama (BSS) Bölümü:

 

Çoğu işletim sistemi, program yürütülmeden önce belleği sıfırlar. Bu nedenle, sıfır başlangıç değeri uygunsa ve işletim sisteminiz bu özelliği destekliyorsa, statik nesnenin başlangıç değeriyle herhangi bir disk alanı harcamanıza gerek yoktur. Bununla birlikte, genel olarak, derleyiciler, statik bir bölümdeki başlatılmamış değişkenleri, onları sıfır ile başlatmışsınız gibi ele alır ve bu nedenle disk alanı tüketir. Bazı işletim sistemleri, bu disk alanı israfını önlemek için ayrı bir bölüm, BSS bölümü sağlar.

BSS bölümü, derleyicilerin genellikle kendileriyle ilişkili açık bir değeri olmayan statik nesneleri koyduğu yerdir. BSS, başlatılmamış bir statik dizi için depolama alanı tahsis etmek için kullanılan sözde işlem kodunu açıklayan eski bir Assembly dili terimi olan bir sembol tarafından başlatılan blok anlamına gelir. Windows ve Linux gibi modern işletim sistemlerinde işletim sistemi, derleyicinin/bağlayıcının tüm başlatılmamış değişkenleri, yalnızca işletim sistemine bölüm için kaç bayt ayırması gerektiğini söyleyen bilgileri içeren bir BSS bölümüne koymasına izin verir. İşletim sistemi programı belleğe yüklediğinde BSS bölümündeki tüm nesneler için yeterli bellek ayırır ve bu belleği sıfırlarla doldurur. Yürütülebilir dosyadaki BSS bölümünün aslında herhangi bir veri içermediğini not etmek önemlidir. BSS bölümü yürütülebilir dosyanın başlatılmamış sıfırlar için yer kaplamasını gerektirmediğinden, büyük başlatılmamış statik diziler bildiren programlar daha az disk alanı kullanır.

Ancak, tüm derleyiciler aslında bir BSS bölümü kullanmaz. Örneğin, birçok Microsoft dili ve bağlayıcı, başlatılmamış nesneleri statik veri bölümüne yerleştirir ve onlara açıkça sıfır başlangıç değeri verir. Microsoft bu şemanın daha hızlı olduğunu iddia etse de, kodunuz büyük, başlatılmamış dizilere sahipse yürütülebilir dosyaları kesinlikle daha büyük yapar (çünkü dizinin her baytı yürütülebilir dosyada sona erer.

 

5.       Yığın(Stack) Bölümü:

Yığın, diğer şeylerin yanı sıra prosedür çağrılarına ve çağrı rutinlerine dönüşe yanıt olarak genişleyen ve daralan bir veri yapısıdır. Çalışma zamanında, sistem tüm otomatik değişkenleri (statik olmayan yerel değişkenler), alt program parametrelerini, geçici değerleri ve belleğin yığın bölümündeki diğer nesneleri aktivasyon kaydı dediğimiz özel bir veri yapısına yerleştirir (sistem uygun bir şekilde adlandırılmıştır, çünkü sistem oluşturur. bir alt program yürütmeye ilk başladığında bir aktivasyon kaydı ve alt program çağırana geri döndüğünde aktivasyon kaydını serbest bırakır). Bu nedenle, bellekteki yığın bölümü çok meşguldür.

Çoğu CPU yığını, yığın işaretçisi adı verilen bir kayıt kullanarak uygular. Bununla birlikte, bazı CPU'lar açık bir yığın işaretçisi sağlamaz ve bunun yerine bu amaç için genel amaçlı bir kayıt defteri kullanır. Bir CPU, açık bir yığın işaretçi kaydı sağlıyorsa, CPU'nun bir donanım yığınını desteklediğini söyleriz; yalnızca genel amaçlı bir kayıt varsa, o zaman CPU'nun yazılım tarafından uygulanan bir yığın kullandığını söyleriz. 80x86, donanım yığını sağlayan bir CPU'ya iyi bir örnektir; MIPS Rx000 ailesi, yazılımda yığını uygulayan bir CPU ailesinin iyi bir örneğidir. Donanım yığınları sağlayan sistemler, yığını yazılımda uygulayan sistemlerden daha az talimat kullanarak genellikle yığındaki verileri işleyebilir. Öte yandan, bir yazılım yığını uygulamasını kullanmayı seçen RISC CPU tasarımcıları, bir donanım yığınının varlığının CPU'nun yürüttüğü tüm talimatları gerçekten yavaşlattığını düşünüyor. Teorik olarak, RISC tasarımcılarının haklı olduğu iddia edilebilir; pratikte ise, 80x86 CPU çevredeki en hızlı CPU'lardan biridir ve bir donanım yığınına sahip olmanın mutlaka yavaş bir CPU'ya sahip olacağınız anlamına gelmediğinin yeterli kanıtıdır.

 

6.       HEAP Bölümü ve Dinamik Bellek Ayırma:

Basit programlar yalnızca statik ve otomatik değişkenlere ihtiyaç duysa da, gelişmiş programlar, program kontrolü altında depolamayı dinamik olarak (çalışma zamanında) tahsis etme ve serbest bırakma yeteneğine ihtiyaç duyar. C ve HLA dillerinde bu amaçla malloc ve free fonksiyonlarını kullanırsınız, C++ new ve delete operatörlerini sağlar, Pascal new ve cancel operatörlerini kullanır ve diğer diller karşılaştırılabilir rutinler sağlar. Bu bellek ayırma yordamlarının birkaç ortak noktası vardır: programcının kaç baytlık depolama alanı tahsis edeceğini istemesine izin verirler, yeni ayrılan depolamaya (yani, bu depolamanın adresine) bir işaretçi döndürürler ve bir kolaylık sağlarlar. artık ihtiyaç kalmadığında depolama alanını sisteme iade etmek için, böylece sistem onu gelecekteki bir ayırma çağrısında yeniden kullanabilir. Dinamik bellek tahsisi, öbek olarak bilinen bir bellek bölümünde gerçekleşir.

Genel olarak, bir uygulama, işaretçi değişkenlerini kullanarak yığındaki verilere atıfta bulunur (gizli veya açık olarak; Java gibi bazı diller, programcının arkasından dolaylı olarak işaretçiler kullanır). Bu nedenle, yığın belleğindeki nesnelere ad yerine bellek adresleriyle (işaretçiler aracılığıyla) başvurduğumuz için genellikle adsız değişkenler olarak atıfta bulunuruz.

İşletim sistemi ve uygulama, program yürütülmeye başladıktan sonra yığın(heap) bölümünü bellekte oluşturur; öbek asla yürütülebilir dosyanın bir parçası değildir. Genel olarak, işletim sistemi ve dil çalışma zamanı kitaplıkları, bir uygulama için yığını korur. Bellek yönetimi uygulamalarındaki farklılıklara rağmen, yığın ayırma ve serbest bırakmanın nasıl çalıştığına dair temel bir fikre sahip olmanız yine de iyi bir fikirdir, çünkü heap yönetimi olanaklarının uygun olmayan bir şekilde kullanılması uygulamalarınızın performansı üzerinde çok olumsuz bir etkiye sahip olacaktır.

 

a)      Bellek Tahsisi(Memory Allocation):

Son derece basit (ve hızlı) bir bellek ayırma şeması, belleğin yığın bölgesine bir işaretçi oluşturan tek bir değişkeni koruyacaktır. Bir bellek ayırma isteği geldiğinde, sistem bu öbek işaretçisinin bir kopyasını yapar ve onu uygulamaya geri döndürür. Daha sonra heap yönetimi yordamları, bellek isteğinin boyutunu işaretçi değişkeninde tutulan adrese ekler ve bellek isteğinin yığın bölgesinde mevcut olandan daha fazla bellek kullanmaya çalışmadığını doğrular (bazı bellek yöneticileri aşağıdaki gibi bir hata göstergesi döndürür: bellek isteği çok büyük olduğunda ve diğerleri bir istisna oluşturduğunda bir NULL işaretçisi). Bu basit bellek yönetimi şemasındaki sorun, belleği israf etmesidir, çünkü uygulamanın, anonim değişkenlerin artık ihtiyaç duymadığı belleği boşaltmasına izin verecek bir mekanizma yoktur, böylece uygulama bu belleği daha sonra yeniden kullanabilir. Heap yönetim sisteminin ana amaçlarından biri, çöp toplama gerçekleştirmek, yani bir uygulama belleği kullanmayı bitirdiğinde kullanılmayan belleği geri kazanmaktır.

Tek yakalama, çöp toplamayı desteklemenin biraz ek yük gerektirmesidir. Bellek yönetimi kodunun daha karmaşık olması gerekecek, yürütülmesi daha uzun sürecek ve yığın yönetim sisteminin kullandığı dahili veri yapılarını korumak için bir miktar ek bellek gerektirecektir. Çöp toplamayı destekleyen bir heap yöneticisinin kolay bir uygulamasını düşünelim. Bu basit sistem, boş bellek bloklarının (bağlantılı) bir listesini tutar. Listedeki her bir boş bellek bloğu, iki çift kelimelik değer gerektirecektir: bir çift kelimelik değer, boş bloğun boyutunu belirtir ve diğer çift kelime, listedeki bir sonraki boş bloğa bir bağlantı (yani, bir işaretçi içerir. ), bkz. Şekil 2:

2.jpgŞekil – 2: Boş bellek bloklarının bir listesini kullanarak Heap yönetimi

 

Sistem yığını bir NULL bağlantı işaretçisiyle başlatır ve boyut alanı tüm boş alanın boyutunu içerir. Bir bellek talebi geldiğinde, yığın yöneticisi önce tahsis talebi için yeterince büyük bir blok olup olmadığını belirler. Bunu yapmak için heap yöneticisi, isteği karşılamak için yeterli belleğe sahip boş bir blok bulmak için listede arama yapmalıdır.

Bir heap yöneticisinin tanımlayıcı özelliklerinden biri, talebi karşılamak için boş bloklar listesinde nasıl arama yaptığıdır. Bazı yaygın arama algoritmaları ilk uygun ve en uygun algoritmalardır. First-fit search, adından da anlaşılacağı gibi, ayırma talebini karşılayacak kadar büyük olan ilk bellek bloğunu bulana kadar blok listesini tarar. En uygun algoritma tüm listeyi tarar ve isteği karşılayacak kadar büyük olan en küçük bloğu bulur. En uygun algoritmanın avantajı, daha büyük blokları ilk sığdırma algoritmasından daha iyi koruma eğiliminde olması ve böylece sistemin geldiklerinde daha büyük müteakip ayırma isteklerini işlemesine izin vermesidir. First-fit algoritması ise, talebi karşılayacak daha küçük bir blok olsa bile, bulduğu yeterince büyük ilk bloğu alır; sonuç olarak, ilk sığdırma algoritması, sistemdeki büyük bellek isteklerini karşılayabilecek büyük boş blokların sayısını azaltabilir.

Bununla birlikte, ilk uyan algoritmanın en uygun algoritmaya göre birkaç avantajı vardır. En bariz avantajı, ilk uyan algoritmasının genellikle daha hızlı olmasıdır. En uygun algoritmanın, tahsis talebini karşılayacak kadar büyük olan en küçük bloğu bulmak için ücretsiz blok listesindeki her bloğu taraması gerekir (tabii ki yol boyunca mükemmel boyutta bir blok bulmadığı sürece). First-fit algoritması ise talebi karşılayacak kadar büyük bir blok bulduğunda durabilir.

First-fit algoritmasının diğer bir avantajı, harici parçalanma olarak bilinen dejenere bir durumdan daha az etkilenme eğiliminde olmasıdır. Parçalanma, uzun bir ayırma ve serbest bırakma istekleri dizisinden sonra gerçekleşir. Unutmayın, yığın yöneticisi bir bellek ayırma isteğini yerine getirdiğinde, genellikle iki bellek bloğu oluşturur - istek için kullanımda olan bir blok ve istek doldurulduktan sonra orijinal blokta kalan baytları içeren bir boş blok arar. Bir süre çalıştıktan sonra, en uygun algoritma, ortalama bir bellek talebini karşılamak için çok küçük olan çok sayıda daha küçük artık bellek blokları üretebilir (çünkü en uygun algoritma aynı zamanda en küçük artık blokları da üretir). Sonuç olarak, heap yöneticisi muhtemelen bu küçük blokları (parçaları) asla tahsis etmeyecektir, bu nedenle etkin bir şekilde kullanılamazlar. Her bir parça küçük olsa da, yığın boyunca birden fazla parça biriktikçe, makul miktarda bellek tüketebilirler. Bu, yeterli boş bellek olmasına rağmen (yığın boyunca yayılmış) yığının bir bellek ayırma talebini karşılayacak kadar büyük bir bloğa sahip olmadığı bir duruma yol açabilir. Bu durumun bir örneği için sonraki sayfada Şekil 3'te bakın.

3.jpg    Şekil – 3: Bellek parçalanması

 

 

İlk uyan ve en uygun algoritmalara ek olarak, başka bellek ayırma stratejileri de vardır. Bazıları daha hızlı çalışır, bazıları daha az (bellek) ek yüküne sahiptir, bazılarının anlaşılması kolaydır (ve bazıları çok karmaşıktır), bazıları daha az parçalanma üretir ve bazıları bitişik olmayan boş bellek bloklarını birleştirme ve kullanma yeteneğine sahiptir. Bellek/yığın yönetimi, bilgisayar biliminde en çok çalışılan konulardan biridir; bir programın faydalarını diğerine göre öven önemli bir literatür var.

 

a)      Çöp toplama(Garbage Collection)

Hafıza tahsisi, hikayenin sadece yarısıdır. Bir bellek ayırma rutinine ek olarak, yığın yöneticisinin, bir uygulamanın gelecekte yeniden kullanmak için artık ihtiyaç duymadığı belleği döndürmesine izin veren bir çağrı sağlaması gerekir. Örneğin, C ve HLA'da bir uygulama bunu free işlevi çağırarak gerçekleştirir.

 

İlk bakışta, free yazmak için çok basit bir fonksiyon gibi görünebilir. Görünüşe göre tek yapılması gereken, daha önce tahsis edilmiş ve şimdi kullanılmayan bloğu free listenin sonuna eklemek. Bu önemsiz free uygulamasıyla ilgili sorun, yığının çok kısa sürede kullanılamaz hale gelecek şekilde parçalanmasını neredeyse garanti etmesidir. Şekil 4'deki durumu göz önünde bulundurun.

4.jpg    Şekil 4: Bir bellek bloğunu serbest bırakma

 

 

Önemsiz bir uygulama, serbest bırakılacak bloğu alır ve onu serbest listeye eklerse, Şekil 4'deki bellek organizasyonu üç serbest blok üretir. Bununla birlikte, bu üç bloğun tümü bitişik olduğundan, yığın yöneticisi bu üç bloğu gerçekten tek bir serbest blokta birleştirmelidir; bunu yapmak, yığın yöneticisinin daha büyük bir isteği karşılamasını sağlar. Ne yazık ki, genel bir perspektiften bakıldığında, bu birleştirme işlemi, sistemin serbest bıraktığı bloğa bitişik boş blok olup olmadığını belirlemek için basit yığın yöneticimizin serbest blok listesini taramasını gerektirir. Bitişik boş blokları birleştirmek için gereken çabayı azaltan bir veri yapısı bulmak mümkün olsa da, bu tür şemalar genellikle yığındaki her blok için ek ek baytların (genellikle sekiz veya daha fazla) kullanımını içerir. Bunun makul bir değiş tokuş olup olmadığı, bellek tahsisinin ortalama boyutuna bağlıdır. Yığın yöneticisini kullanan uygulamalar küçük nesneleri ayırma eğilimindeyse, her bir bellek bloğu için ekstra ek yük yığın alanının büyük bir yüzdesini tüketebilir. Bununla birlikte, tahsislerin çoğu büyükse, o zaman birkaç bayt ek yükün pek bir önemi olmayacaktır.

a)      İşletim Sistemi ve Bellek Tahsisi:

Heap yöneticisinin kullandığı algoritmaların ve veri yapılarının performansı, performans sorununun yalnızca bir parçasıdır. Son olarak, heap yöneticisinin işletim sisteminden bellek blokları istemesi gerekir. Olası bir uygulamada, işletim sistemi tüm bellek ayırma isteklerini işler. Başka bir olasılık da yığın yöneticisinin, uygulamanızla bağlantı kuran bir çalışma zamanı kitaplığı yordamı olmasıdır; yığın yöneticisi işletim sisteminden büyük bellek blokları ister ve ardından uygulamadan bellek istekleri geldikçe bu bloğun parçalarını dağıtır.

İşletim Sistemine doğrudan bellek ayırma istekleri yapmanın sorunu, İşletim Sistemi API çağrılarının genellikle çok yavaş olmasıdır. Bir uygulama, yaptığı her bellek isteği için işletim sistemini çağırırsa, uygulama birkaç bellek ayırma ve ayırma çağrısı yaparsa, uygulamanın performansı büyük olasılıkla düşecektir. OS API çağrıları çok yavaştır, çünkü genellikle çekirdek modu ile CPU'daki kullanıcı modu (hızlı değildir) arasında geçiş yapmayı içerirler. Bu nedenle, uygulamanız bellek ayırma ve serbest bırakma yordamlarına sık sık çağrı yapıyorsa, işletim sisteminin doğrudan uyguladığı bir yığın yöneticisi iyi performans göstermeyecektir.

Bir işletim sistemi çağrısının yüksek ek yükü nedeniyle çoğu dil, dilin çalışma zamanı kitaplığı içinde kendi malloc ve free sürümlerini (veya onlara ne diyorlarsa) uygular. İlk bellek tahsisinde, malloc yordamı işletim sisteminden büyük bir bellek bloğu talep edecek ve ardından uygulamanın malloc ve serbest yordamları bu bellek bloğunu kendileri yönetecektir. Malloc işlevinin orijinal olarak oluşturduğu blokta yerine getiremeyeceği bir tahsis talebi gelirse, malloc işletim sisteminden başka bir büyük blok (genellikle talepten çok daha büyük) talep eder ve bu bloğu serbest listesinin sonuna ekler. Uygulamanın malloc ve free rutinlerine yapılan çağrılar işletim sistemini yalnızca ara sıra çağırdığından, bu, işletim sistemi çağrılarıyla ilişkili ek yükü önemli ölçüde azaltır.

Ancak, bir önceki paragrafta gösterilen prosedürün çok uygulamaya ve dile özgü olduğunu aklınızda bulundurmalısınız; bu nedenle, yüksek performanslı bileşenler gerektiren yazılımlar yazarken malloc ve free'nin nispeten verimli olduğunu varsaymanız tehlikelidir. Yüksek performanslı bir heap yöneticisi sağlamanın tek taşınabilir yolu, uygulamaya özel bir dizi rutini kendiniz geliştirmektir.

Çoğu standart head yönetimi işlevi, tipik bir program için makul düzeyde çalışır. Bununla birlikte, özel uygulamanız için, çok daha hızlı olan veya daha az bellek ek yükü olan özel bir işlevler dizisi yazmak mümkün olabilir. Uygulamanızın ayırma yordamları, programın bellek ayırma modellerini iyi anlayan biri tarafından yazılmışsa, ayırma ve ayırma işlevleri, uygulamanın isteklerini daha verimli bir şekilde işleyebilir.

 

b)      Head Bellek Yükü:

Bir head yöneticisi genellikle iki tür ek yük sergiler: performans (hız) ve bellek (alan). Şimdiye kadar, tartışma esas olarak bir head yöneticisinin performans özelliklerini ele aldı; şimdi dikkatimizi head yöneticisiyle ilişkili bellek ek yüküne çevirmenin zamanı geldi.

Sistemin tahsis ettiği her blok, uygulamanın talep ettiği depolamanın üzerinde ve ötesinde bir miktar ek yük gerektirecektir. En azından, head yöneticisinin ayırdığı her blok, bloğun boyutunu takip etmek için birkaç bayta ihtiyaç duyar. Daha gösterişli (daha yüksek performanslı) şemalar ek baytlar gerektirebilir, ancak tipik bir ek bayt sayısı 4 ile 16 arasında olacaktır. Head yöneticisi bu bilgiyi ayrı bir dahili tabloda tutabilir veya blok boyutunu ve diğer herhangi bir belleği ekleyebilir. -Yönetim bilgilerini doğrudan bloklara ayırır.

Bu bilgileri dahili bir tabloya kaydetmenin birkaç avantajı vardır. İlk olarak, uygulamanın yanlışlıkla burada saklanan bilgilerin üzerine yazması zordur; verileri head bellek bloklarına eklemek, uygulamanın bu kontrol bilgilerini arabellek aşımları veya yetersizlikleri ile silmesine karşı da koruma sağlamaz (böylece bellek yöneticisinin veri yapılarını bozar). İkincisi, bellek yönetimi bilgisini dahili bir veri yapısına koymak, bellek yöneticisinin belirli bir işaretçinin geçerli olup olmadığını (head yöneticisinin tahsis ettiğine inandığı bir bellek bloğunu gösterip göstermediğini) belirlemesine olanak tanır.

Head yöneticisinin tahsis ettiği her bloğa kontrol bilgisi eklemenin avantajı, bu bilgiyi bulmanın çok kolay olmasıdır, çünkü bellek yöneticisi bu bilgiyi genellikle tahsis edilen bloğun hemen önüne yerleştirir. Head yöneticisi bu bilgiyi dahili bir tabloda tuttuğunda, bilgiyi bulmak için bir tür arama işlemi gerektirebilir.

Head yöneticisiyle ilişkili ek yükü etkileyen başka bir sorun da ayırma ayrıntı düzeyidir. Çoğu head yöneticisi, depolamayı bir bayt kadar küçük bloklar halinde ayırmanıza izin vermesine rağmen, çoğu bellek yöneticisi aslında birden fazla minimum sayıda bayt ayıracaktır. Bu minimum miktar, bellek yöneticisinin desteklediği ayırma ayrıntı düzeyidir. Genel olarak, bellek ayırma işlevlerini tasarlayan mühendis, öbek üzerinde ayrılan herhangi bir nesnenin o nesne için makul şekilde hizalanmış bir bellek adresinde başlayacağını garanti edecek bir ayrıntı düzeyi seçer. Bu nedenle, çoğu yığın yöneticisi, bellek bloklarını 4-, 8- veya 16 baytlık bir sınırda tahsis eder. Performans nedenleriyle, birçok yığın yöneticisi her ayırmaya genellikle 16, 32 veya 64 baytlık tipik bir önbellek satırı sınırında başlar.

Parçacık düzeyi ne olursa olsun, uygulama, head yöneticisinin ayrıntı düzeyinden daha az sayıda bayt isterse veya ayrıntı düzeyi değerinin katı olmayan bir sayıda bayt isterse, head yöneticisi fazladan depolama baytları ayırır, böylece tam ayırma çift kat olur. ayrıntı düzeyi değeri. Bu nedenle, head yöneticisinin ayırdığı minimum boyutlu bloğu doldurmak için her tahsis isteğine iliştirilmiş birkaç istenmemiş bayt olabilir. Tabii ki, bu miktar head yöneticisine (ve hatta belirli bir head yöneticisinin sürümüne) göre değişir, bu nedenle bir uygulama asla talep ettiğinden daha fazla kullanılabilir belleğe sahip olduğunu varsaymamalıdır; zaten bunu yapmak aptalca olurdu, çünkü uygulama daha fazlasına ihtiyaç duyduğunda ilk ayırma çağrısında daha fazla bellek talep edebilirdi.

Head yöneticisinin, isteğin ayrıntı boyutunun bir katı olmasını sağlamak için ayırdığı fazladan bellek, dahili parçalanma adı verilen başka bir parçalanma biçimiyle sonuçlanır. Dış parçalanma gibi, iç parçalanma da sistem genelinde gelecekteki ayırma isteklerini karşılayamayan küçük miktarlarda bellek kaybına neden olur. Rastgele boyutlu bellek tahsisleri varsayıldığında, her bir tahsiste meydana gelecek ortalama dahili parçalanma miktarı, ayrıntı boyutunun yarısı kadardır. Neyse ki, ayrıntı boyutu çoğu bellek yöneticisi için oldukça küçüktür (tipik olarak 16 bayt veya daha az), bu nedenle binlerce ve binlerce bellek tahsisinden sonra dahili parçalanma nedeniyle yalnızca birkaç düzine kadar kilobayt kaybedersiniz.

Tahsis ayrıntı düzeyi ve bellek kontrol bilgisi ile ilişkili maliyetler arasında, tipik bir bellek talebi, artı uygulamanın talep ettiği şey ne olursa olsun, 4 ila 16 bayt gerektirebilir. Büyük bellek ayırma istekleri (yüzlerce veya binlerce bayt) yapıyorsanız, ek yük baytları yığındaki belleğin büyük bir yüzdesini tüketmez. Ancak, çok sayıda küçük nesne ayırırsanız, dahili parçalanma ve kontrol bilgileri tarafından tüketilen bellek, yığın alanınızın önemli bir bölümünü temsil edebilir. Örneğin, veri bloklarını her zaman 4 baytlık sınırlarda tahsis eden ve kontrol amacıyla her tahsis talebine iliştirdiği tek bir 4 baytlık uzunluk değeri gerektiren basit bir bellek yöneticisini düşünün. Bu, minimum bir yığın yöneticisinin her ayırma için ihtiyaç duyacağı depolama birimi sayısı sekiz bayttır. Tek bir bayt ayırmak için bir dizi malloc çağrısı yaparsanız, uygulama ayırdığı belleğin neredeyse yüzde 88'ini kullanamayacaktır. Her ayırma isteğinde 4 baytlık değerler ayırsanız bile, yığın yöneticisi genel gider amaçları için belleğin yüzde 67'sini kullanır. Ancak, ortalama ayırmanız 256 baytlık bir bloksa, ek yük, toplam bellek ayırmanın yalnızca yaklaşık yüzde 2'sini gerektirir. Hikayeden alınacak ders: Tahsis talebiniz ne kadar büyük olursa, kontrol bilgilerinin ve dahili parçalanmanın yığınınız üzerindeki etkisi o kadar az olur.

Bu yazı ile kendi kodunuzdaki bu potansiyel sorunun farkına varmanızı sağlamıştır.

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...