StringBuilder’ ın Performansa Etkisi ve Kullanım Deneyimi
Merhabalar, bu yazım “Neden string yerine string builder kullanmalıyız?” sorusu üzerine olacak. Beni bu yazıyı yazmaya iten sebep üniversitede yapay zeka ödevi için yazdığım genetik algoritma kodunun yavaş çalışması oldu. Sorun sadece yavaş çalışmasıda değildi. Yazdığım kodun String işlemi yapan kısmını(kromozom oluşturma, çaprazlama, mutasyon) projeyi pythonla yazan ev arkadaşımın koduyla karşılaştırdık. Bu arada string işlemi derken String bir ifadeye ekleme işleminden söz ediyorum. Yaptığımız String işlemleri benzerdi. Sorun da tam burdaydı. Pythonda hızlı çalışan kısım C#’ta aşırı yavaş kalıyordu.
Tabi aklımı “C# bu kadar mı yavaş”, ”Vay be! Pythona bak, adamlar yapmış” gibi düşünceler sarmaya başladı. Bir yandan da o da string bu da string ne farkı var diye düşüne düşüne bu durumu kafaya taktım ve araştırmaya başladım.
Başta açıkçası pek fazla umudum yoktu. Ama araştırmam sonuç vermişti. Stackoverflow ve diğer mecralarda çoğu kişi eğer String nesnesi sürekli değişime uğruyorsa string builder kullanılması gerektiğini yazıyordu. Kodumda String kullandığım yerleri StringBuilder ile değiştirdim. Değiştirdiğim yerler kromozomu oluşturduğum, çaprazlama yaptığım ve mutasyon işlemini gerçekleştirdiğim fonksiyonlardı. Artık büyük an gelmişti. Kodu çalıştırdım ve o an yaşadığım sevinci tarif edemem. Arkadaşın odasına “Evreka Evreka” diye koşturduğumu hatırlıyorum. Eskisine göre kat kat hızlı çalışıyordu. Önemsiz gibi görünen değişken tanımlamanın performansa ne kadar etki ettiğini ve ihtiyaca göre kullanmanın ne kadar önemli olduğunu bu projede görmüştüm.
string kromozom = ””;for(int i=0;i<1000000;i++){
kromozom += "1";
}
Şimdi gelelim String yerine StringBuilder kullanmamın performansa neden bu kadar etki ettiğine. Yukarıda oluşturduğum basit kod parçasında görüldüğü gibi kromozom değişkenine bir milyon tane 1 ekleme işlemi yapılıyor. Döngünün ilk adımında ifade “1” değerini alıyor, ikinci adımında “11”, bir milyona kadar teker teker String ifadesinin değeri bu şekilde değişiyor. Aslında değişiyormuş gibi görünüyor ama sadece referans gösterdiği nesne değişmiş oluyor. Nurtopu gibi sadece son hali işimize yaracayacak olan nesnenin 999.999 tane daha nesnesi oluştu. Referans gösterdiği ise en son oluşan nesne olan 1 milyonuncu nesne oldu. Şimdi ne kadar performansı olumsuz yönde etkilediğini ve belleği şişirdiğini görebiliyoruz. Aşağıda oluşturduğum örnekte bu durum daha net anlaşılabilir.
StringBuilder, String’in aksine “mutable” yani değiştirilebilir bir nesnedir. Her bir değişiklik işlemimizde yeni bir nesne oluşturmak yerine mevcut olan üzerinde değişiklik yapar. Şimdi bu nesnenin methodlarını inceleyelim. Append metodu ile ekleme işlemi yapabiliriz. Bu method ile gönderdiğimiz değer String ifadeye çevrilir ve birleştirme işlemini gerçekleştirir. AppendLine methodu ile de String ifadeyi ekledikten sonra bir alt satıra geçme işlemini yapar. Aşağıda StringBuilder kullanımını görebilirsiniz.
StringBuilder kromozom = new StringBuilder();
kromozom.Append(1); // 1
kromozom.Append(1); // 11
kromozom.Append(1); // 111
kromozom.Append(1); // 1111
kromozom.AppendLine("Yunus"); // 1111Yunus
kromozom.Append("Unver");
//1111Yunus
//Unver
Son olarak Benchmark testini sizinle paylaşmak istiyorum. Aşağıya linkini bırakıyorum ama kısaca bahsedeceğim. Bu testte String ile StringBuilder’ı karşılaştırmak için 200.000 sayı içeren bir liste oluşturmuşlar. İlk olarak bütün listeyi döngüde dönerken “+=” ile String üzerine ekleme işlemi yapmışlar. Bu işlem tam olarak 2 dakika 30 saniye sürmüş. Bunun nedeninide doğal olarak her ekleme işleminde sürekli yeni bir String nesnesi oluşturması ve bellek kullanımının buna bağlı olarak artması şeklinde göstermişler. Aşağıya String için yapılan testin kodunu ekledim.
List hugeList = Enumerable.Range(1000, 200000).Select(n => n.ToString()).ToList();String concatResult = "";
foreach (String value in hugeList) {
concatResult += value;
}
Aşağıya StringBuilder için yapılan testin kodunuda ekledim.
List hugeList = Enumerable.Range(1000, 200000).Select(n => n.ToString()).ToList();StringBuilder stringBuilder = new StringBuilder();
String stringBuilderResult = "";
foreach (String s in hugeList) {
stringBuilder.Append(s);
}
stringBuilderResult = stringBuilder.ToString();
Sonrasında ise StringBuilder nesnesine aynı testi uygulamışlar. Bu sefer bu nesneye ait olan Append methodunu kullanmışlar. Yapılan işlem tam olarak 99 mili saniye sürmüş. Aradaki muazzam farkı görebiliyor musunuz? Biri 2 dakika 30 saniye diğeri sadece 99 milisaniye sürüyor. Sürekli değişen ifadelerde StringBuilder kullanmanın ne kadar fayda sağladığını açık bir şekilde görüyoruz.
Yazımı burada sonlandırıyorum. Aşağıya incelemek istersiniz diye benchmark testinin linkini ve yapay zeka projesinin github linkini de ekledim.
String vs StringBuilder Benchmark Testi
Yapay Zeka Projesi Github Linki