İlkay İlknur

just a developer...

XML Serializer ve Memory Problemi

Yazılım geliştirirken kullandığımız kütüphanelerin sadece kullanımlarını değil aynı zamanda implementasyon detaylarını da iyice araştırmamız ve öğrenmemiz gerek. Aksi takdirde hiç beklenmedik yerlerde hiç beklenmedik sonuçlarla karşılaşmak mümkün.

Bu yazımızda konumuz, dışarıdan oldukça masum görünen ancak içerisinde bir şeytan yatan XmlSerializer tipi :) XmlSerializer tipi bildiğiniz üzere .NET Framework içerisinde XML serialize ve deserialize işlemleri için kullandığımız bir tip. Kullanımı da oldukça basit. Örneğin, aşağıdaki koda baktığınızda Foo tipini bir MemoryStream içerisine serialize ettiğimizi göreceksiniz.

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 500000; i++)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Foo),
new XmlRootAttribute("Test"));
                Foo foo = new Foo()
                {
                    Bar = "bar"
                };
                serializer.Serialize(ms, foo);
            }
        }
    }
}

public class Foo
{
    public string Bar { getset; }
}

XmlSerializer'ın şeytanlığı onunla yaptığınız işlemin sayısı arttıkça ortaya çıktığından dolayı serialize işlemini 500.000 kez tekrarladım. Uygulamayı çalıştırıp, uygulamanın kullandığı memory miktarına baktığımda hiç beklenmedik bir sonuçla karşılaşıyoruz. Yazdığımız kod oldukça basit bir kod olmasına rağmen uygulamanın kullandığı memory daha 500000. iterasyona gelmeden 1,7 GB'a kadar çıkmakta ve uygulamamız OutOfMemoryException vermekte.

Uygulamanın kullandığı memory

Şimdi gelelim sorunun kaynağına. XmlSerializer tipi serialize ve deserialize işlemleri sırasında performansı arttırma amacıyla sizin serialize ettiğiniz her tip için arka planda dinamik olarak bir assembly oluşturuyor. Performansı arttırmak için yapılan bu hareket gördüğünüz üzere doğru kullanılmadığı durumlarda başımıza iş açabiliyor. MSDN'e göre eğer XmlSerializer'ın XmlSerializer.XmlSerializer(Type) ve XmlSerializer.XmlSerializer(Type, String) constructorlarını kullanırsanız .NET Framework arka planda yarattığı assemblyleri cache'liyor ve her XmlSerializer yarattığınızda tekrardan bir assembly yaratmıyor. Bu şekilde bir kullanım yaparsak bakalım uygulamamızın memorysi ne durumda oluyor. Yeni durumda kodumuz aşağıdaki gibi değişecek.

class Program
{
    static void Main(string[] args)
    {
        for (int i = 0; i < 500000; i++)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Foo));
                Foo foo = new Foo()
                {
                    Bar = "bar"
                };
                serializer.Serialize(ms, foo);
            }
        }
    }
}
[XmlRoot(ElementName = "Test")]
public class Foo
{
    public string Bar { getset; }
}

Uygulamayı çalıştırdığımızda karşılaştığımız sonuçla bir önceki durumda karşılaştığımız sonuç arasında dağlar kadar fark var.

Yeni durumda memory durumu

Gördüğünüz gibi yeni durumda uygulamanın kullandığı memory 5.5 MB'a kadar düştü. Nerede 1.7 GB  nerede 5.5 MB :) Hem de çok ufak bir optimizasyonla.

Diyelim ki XmlSerializer'ın yukarıdaki optimize edilmiş contructorlarınızı kullanamıyorsunuz ve mecburen arka planda cache'leme yapmayan versiyonlarını kullanıyorsunuz. Yukarıdaki gibi memory problemi yaşamamak için önerilen yöntem yarattığınız XmlSerializer instancelarını sizin cachelemeniz. Yani burada .NET Framework'ün yapmadığını sizin yapmanız gerekiyor.

Gördüğünüz gibi yazılım geliştirirken ufak detaylar bazen oldukça önemli olabiliyor ve sizi büyük dertlerden kurtarıyor. Bu yüzden kullandığımız tiplerin, programlama dillerinin detaylarına olabildiğimiz kadar hakim olmakta fayda var. Hakimiyeti arttırmanın yolları da bol bol MSDN'i okumak ve Reflector'la kod detaylarına bakmaktan geçiyor :)



Yorum Gönder