info@ubfsoft.com
+90 (850) 302-5560
Sakarya Teknokent

Memory Management ve Performans Teknikleri

Bu makalede Memory Management hakkında ön bilgi ve dinamik memory kullanımları için performans artırıcı yöntemlere değineceğiz. Makale genelinde kodlama örnekleri C/C++ dili ile örneklense de diğer dillerde de karşılıkları ekseriyetle bulunmaktadır. Anlatmak istediğimiz temel konu aslında dillerden bağımsız olarak CPU,RAM ve İşletim Sistemleri hakkında standart olan bir konudur.

Bu konu aslında yazılımın temel işleyişinde verinin işlenmesinin hayat bulduğu işlemler bütününü kapsamaktadır. Bellek yönetimi en basitinden değişken tanımlamalarımızdan başlayan ve daha karmaşık veri temsillerinin gerçekleştirilmesine yönelik yapılan çalışmaların tamamını kapsamaktadır.

Bellek yönetimi konusunda güncel olarak kodlama yaparken kullandığımız değişken tanımlamalarımız ve kullanımlarımız ile bellek tahsisi ve tekrar serbest bırakılmaları açısından en temel memory management işlevlerini yerine getirmekteyiz.

Bu işlemler hangi yazılım dilinde olursa olsun temel işlemler olarak sağlanmaktadır. Kısaca açmış olduğumuz değişkenin bellekte yer tahsisi, uzunluk ve işi bittiğinde bellekten silinmesi işlemleri ile hiç uğraşmayız. Ancak bunların dışında daha özel veri yapıları veya işlediğimiz konuya göre çok özgün veri temsilleri için dinamik bellek tahsisleri gerekebilmektedir.

Dinamik memory kullanımı ile kullandığımız değişkenler dışında ihtiyacımız olan bellek tahsislerini kendimiz yapabiliriz. Buffer olarak adlandırdığımız ayrılmış bellek bölgesi, adresi ve uzunluğu belli olan byte dizisidir.

Dinamik Memory kullanımı 3 aşamalı bir sürecin yönetimidir. Bunlar Tahsis etmek, kullanmak ve işi bittiğinde serbest bırakmak gibi en temel işlemlerden oluşmaktadır.

Bir veri bellekte belirli bir adreste ve belirli bir uzunlukta temsil edilir. 1 byte uzunluğunda dinamik yer tahsisi de yapabilirsiniz, 10 MB uzunluğunda bellek tahsisi de yapabilirsiniz.

Yazılmış bir uygulama hangi işletim sisteminde olursa olsun çalışırken bir bellek bölgesine yüklenir ve çalıştırılır. Process adı verilen uygulama aslında kendi içinde segment denilen ayrı bölümlerden oluşur.

Bu segmentler Code Segment, Data Segment, Stack Segment, Extra Segment olarak bilinir. Bir uygulama (Process) belleğe yüklenirken bellek tahsisi yapılır ve her segment verisi ilgili tahsis edilen segment alanına yüklenir. Tabiki her segment için veri yükleme yapılmaz. Çünkü bazı segmentler kısmen yüklenir veya daha çok çalışma anında kullanılır. Örnek olarak Code Segment makine kodlarını ihtiva eder ve process’in ilk belleğe yüklenmesinde belleğe yüklenir. Oysa Stack Segment process’in makine kodları CPU tarafından işletilmeye başlayınca kullanılmaya başlar.

Yazılım geliştirirken bizim değişken kullanımlarımız genelde process içinde Data Segment ve Stack Segment dediğimiz alanlardan tahsisler yapılarak çalışır. Data segment bu anlamda Heap olarakta adlandırılır. Ve genellikle global değişken değerleri ve dinamik tahsisler yapmak için kullanılır. Fonksiyon parametreleri (formal değişkenler) ise fonksiyon çağrımlarına göre tahsis edilir ve fonksiyonlardan çıkıldığında serbest bırakılır. Bu değişkenler için Stack Segment kullanılır. Stack segment’in çalışma şekli ise LIFO yöntemi ile çalışır. Stack Segment yazılım belleğe yüklenirken belirlenir ve boyutu ayarlanabilir. Fonksiyonların çağrılmalarına göre ilk stack segment içinde ilgili değişkenler kadar ilerlenir ve silinmesi gerekirken ise geri doğru gelinir. Segment içinde görünmez bir imleç gibi pozisyonu değişir. Stack Segment’in yönetimi için yazılım geliştiriciler herhangi bir işlem yapmazlar. Burası Code Segment te bulunan ve çalışan PUSH ,POP gibi makine kodları sayesinde kullanılırlar.

Bir process’ in kendi içinde segmentleri olduğunu söylemiştik. Günümüzde İşletim Sistemleri Multi process yetenekleri ile aynı anda birçok uygulamayı çalıştırabilmektedir.

Bir process başka bir process’in verilerini okuyabilirmi veya ilgili iç segmentlerine erişip yazabilirmi? Teorik olarak mümkün diyebiliriz. Ama genel kullanımda korumalı mod(Protected Mode) dediğimiz CPU seviyesinde erişim engeline takılırlar. Dinamik memory kullanımı için aslında yazılımcıya Data Segment ve Diğer tüm process’lerin kullanımının dışında kalan External Memory dediğimiz kısım kalmaktadır. Yazılımcılar işte burayı dinamik tahsislerle kullanabilirler.

Bunların dışında adı çokça gecen Shared Memory konusuna da kısaca değinmek gerekmektedir. Shared Memory external memory olarak bilinen kısımda birden fazla farklı uygulama yazılımının aynı bellek bölgesine erişimi için tahsis edilen bellek bölgesine verilen isimdir. Tabiki external bölgede tahsis edilen bellek bölgesinin diğer process’ lere paylaştırılması zorunluluğu yoktur. Ancak Diğer process’ler bu bellek bölgesinden okuma ve yazma yapabilirler. Bu bilginin aslında güvenlik açısından değerlendirilmesi de yazılan programlar açısından önemlidir. Yazılım geliştiricinin bunu bilmesi ve external alanda tahsis edilen bellek bölgesinde güvenlik açısından kritik olan verilerini tutmaması güvenlik açısından önemli bir avantaj oluşturmaktadır. Örneğin bir Web Browser yazdığınızı düşünün session bilgilerinin external memory de tutulması güvenlik zaafiyeti doğurabilir.

Bir başka yetenek olarak Virtual Memory konusuna da değinmek gerekirse aslında fiziksel belleğin yetmediği durumlarda disk üzerinden normal memory erişim işlemlerini yapabilmemize olanak sağlayan adresleme yeteneğine verilen isimdir. Tabi burada fiziksel bellek adresleri ile sanal bellek adreslerine dönüşme işlemleri gibi bir çok işlem yapılmaktadır. MMU(Memory Management Unit) arabirimi bu sanal adresleri bizim için dönüştürür ve aslında fiziksel memory’den çok daha büyük bellek alanlarını tahsis edebilmemize olanak sağlar. Bir başka bakış açısı ile fiziksel bellek tükense dahi bellek tahsisi işlemlerine devam edebilmekteyiz. Düşük RAM’li bilgisayarlarda Harddisk lambasının çokça yanıp sönmesinin tek nedeni virtual memory erişimi değildir. Fakat büyük oranda virtual memory kullanımından kaynaklamaktadır.

Performans için yapabileceklerimiz

Buraya kadar anlattıklarımızla aslında iç işleyişinde daha çok detay barındıran bir konuyu en sade şekliyle anlatmaya çalıştık. Bunların dışında Dinamik Memory kullanımı açısından, işlemciyi daha az meşgul eden ve performans kazandırabilecek birkaç teknikten bahsedebiliriz.

1 - İlk olarak dinamik hafıza tahsisi sürekli olarak yapılması gerekiyorsa bu işlemi tamponlu hale getirebiliriz.Örnek olarak dinamik bir dizi oluştururken diziye her ekleme yapıldığında bellek tahsisi yapmak yerine 100 elemanlık tahsisat yaparak ve dolduğunda yeni bir 100 elemanlık yer tahsisatı daha yaparak devam eden bir algoritma şeklinde düzenleyebiliriz. Bu bir parça fazla memory kullanımımızı artırsa da çalışma anı daha hızlı işlem yapmamızı sağlayacaktır.

2 - Bellek üzerinde bir bölgeden başka bir bölgeye veri kopyalama işlemlerinde memcpy,memmove gibi fonksiyonları kullanabiliriz. Kopyalamayı döngüyle yapmaktan çok daha hızlı çalışacaktır. İki bellek bölgesi(buffer) kıyaslama için memcmp veya ilgili bellek bölgesinin bütün byte değerlerini belli bir değerle doldurmak için memset fonksiyonlarını kullanabilirsiniz. Bahsi geçen bu fonksiyonların performanslı olmaları işlemcinin normal bellek erişimlerinden ziyade MMU, MMX ve SIMD yeteneklerinden faydalanıyor olmalarından kaynaklanmaktadır.

char *pBufferSource = (char*)malloc(1000);  //1000 byte uzunluğunda bellek tahsisi

if( pBufferSource!= NULL ) {
	
   char *pBufferTarget = (char*)malloc(1000);  //1000 byte uzunluğunda bellek tahsisi

   if(  pBufferTarget != NULL ) {
	
      //Source Bufferin 900 byte alanını ‘A’ karakteri ile doldur 
      memset( pBufferSource , ‘A’ , 900 );  
	
      //Source Bufferin 900 byte Target Buffer alanına kopyala
      memcpy( pBufferTarget , pBufferSource , 900  );
	
      //işimiz bittiği için target buffer alanını serbest bırakıyoruz.
      free( pBufferTarget );  
   }

   //işimiz bittiği için source buffer alanını serbest bırakıyoruz.
   free( pBufferSource  ); 
   
}

3 - Bunların dışında döngü değişkenleri olarak kullandığımız değişkenlerin bellekte tahsis edilmesi yerine CPU yazmaçları üzerinde tahsis edilmesini sağlayarak sayaç değişkeninin her bir arttırılması işleminde CPU’nun veri yolunu kullanarak memory erişimini ortadan kaldırmış olursunuz. Ve böylece döngünüzün daha hızlı dönmesini sağlayabilirsiniz.

//register ön eki ile değişkenin bellek yerine CPU yazmacı kullanmasını belirtiyoruz
register int x = 0;  
for( i = 0; i < 5000000; i++ ) {
   printf( “%d\n” , i );
}

4 - Döngü içi değişken tanımlamalarından kaçınmak yine bir başka bellek tahsisinin her döngü icrasında yapılmamasını sağlayacaktır. Başka bir deyişle döngü başlamadan gerekli değişkenleri oluşturup döngü içinde kullanmak, bu yöntemde yine bize performans kazandıracaktır.

int s = 0;
while( i < 1000000 ) {
   //buradada  int s = i * 3; şeklinde tanımlama yapabilirdik ancak döngüden önce tahsis ettik
   s = i * 3; 
   printf( “%d\n” , s );
   i++;
}

Performans açısından yukardaki birkaç yöntemle algoritmalarınızı daha verimli hale getirebilirsiniz. Bazen yazılımlarda en yoğun döngü yapan kısımlarda bu tür iyileştirme çalışmaları ile yazılım çalışma maliyetlerini düşürebilirsiniz.

Örnekleyecek olursak bir backend yazılımınızın verdiği hizmeti 100 sunucu ile veriyorsanız, performans iyileştirme yaptığınızda aynı kapasite de hizmeti belki 80 sunucuyla sağlayabilir, aynı zamanda ekonomik açıdan da işletme maliyetlerinizi düşürebilirsiniz.

Bu makalemizde genel anlamda memory management alanında temel konulara değindik, Tek bir makaleye sığmayacak kadar teknik alt öğeyi içinde barındıran bir konu olmasından dolayı bu makalenin sadece ön bilgi niteliğinde değerlendirilmesi daha doğru olacaktır. Yine de Memory Management işlemlerini, veriyapıları ve algoritmalar konusundan bağımsız olarak düşünmemek daha kapsayıcı bir yaklaşım olacaktır.

Bu konularla daha fazla ilgilenmek ve bilgi edinmek isteyenler için faydalı olacağını düşündüğüm birkaç kaynağı sizlerle paylaşmak istiyorum.

https://en.wikipedia.org/wiki/Memory_management

https://en.wikipedia.org/wiki/Intel_8086

https://www.intel.com/content/dam/www/public/us/en/documents/research/1997-vol01-iss-3-intel-technology-journal.pdf

https://www.nxp.com/docs/en/supporting-information/TPQ2CH09.pdf

İletişime Geçelim