Java Obje Dönüştürme Kütüphaneleri Performans Karşılaştırması
Çok katmanlı mimarilerde bir objenin başka bir objeye dönüştürülmesi sık olarak karşılaşılan durumlardan bir tanesidir. Gerek entity objelerinin dış servislere açılırken ona uygun olan bir objeye dönüştürülmesi, gerekse istek olarak alınan objelerin uygun bir şekilde input objelerine dönüştürülmesi gerekmektedir.
Bu dönüşüm birçok şekilde gerçekleştirilebilir. En basit yöntemlerden bir tanesi iki objeyi getter-setter vasıtasıyla field bazlı dönüştürme olurken, bunun yanında verilen iki objeyi birbirine otomatik olarak dönüştüren kütüphaneler de kullanılabilmektedir.
Hangi yöntemle dönüştürüleceği ise iki açıdan değerlendirilebilir:
1) Performans
2) Geliştirme zamanı
Bazı obje mapleme kütüphaneleri şunlar: BeanUtils, MapStruct, Dozer, Orika ve Selma. Bu kütüphanelerden bazıları reflection kullanırken (BeanUtils, Dozer) bazıları ise compile time code generation (MapStruct, Orika, Selma) kullanırlar.
Reflection ile code generation arasındaki en büyük fark ise performanstır. Reflection çok maliyetli bir işlemdir, dolayısıyla compile time’da yapılacak bir code generation işlemine kıyasla performans farkı çok fazla olacaktır.
Bu yazıda yukarıda saydığım kütüphanelerin performans karşılaştırmasını yapacağım.
Yukarıdaki MySimpleDto objesini MySimpleResponse objesine dönüştüreceğiz. İlk başta kütüphane kullanmadan setter-getter kullanarak yapalım.
Devamında MapStruct ile yapalım. Bunun için önce bir interface tanımlamamız, ardındansa o interface metodunu kullanmamız gerekmekte. MapStruct kütüphanesi, tanımladığımız interface methodunun kodunu compile time’da generate ederek mapper methodu kendisi oluşturur.
Diğer 4 mapleme yöntemine projenin GitHub reposundan ulaşabilirsiniz.
JMH ile Performans Ölçümü
Java’da benchmark için JMH (Java Microbenchmark Harness) kütüphanesi kullanılabilir. Bu kütüphanede istediğimiz methodları belirli parametrelerle çağırıp, ölçümünü yaptığımız işlemlerin performansı hakkında bilgi edinebiliriz.
Bu karşılaştırmayı yaparken JMH parametreleri olarak şunları kullandım.
Bu konfigürasyonda satır sırasıyla şunları ayarladım:
- 5 ısınma turu yapılacak
- O benchmark 1 forklanarak yapılacak
- Bir operasyonun (benchmark sırasında çağırılan metod) ortalama ne kadar sürede yapıldı
- Zaman ölçü birimi milisaniye olacak
- Benchmark yapılırken metod 100.000 kez çağırılacak ve toplamda bu işlem 3 kere tekrar edilip ortalaması alınacak
Yazdığım 6 farklı benchmark metodu 6 farklı map yöntemine karşılık gelmektedir ve 100.000 kere bir MySimpleDto objesini MySimpleResponse objesine dönüştürme işleminin performans ölçüm sonucu aşağıdaki gibidir:
Yukarıdaki sonuçlara göre objemizi 100.000 kere dönüştürme işlemini sadece ve sadece 0.710 ms’de yapan eski dostumuz setter-getter en hızlı yöntem oluyor.
Ardından Selma ve MapStruct byte code generation işlemi yaptıkları için, elimizle yazdığımız setter-getter’ları compile time’da yazıp runtime’da ise onları çağırıyor. Haliyle aldığı sonuçlar setter-getter ile çok yakın.
En son sırada ise Dozer ve Apache BeanUtils oluyor. Reflection tabanlı obje dönüşümü yaptıkları için diğer yöntemlere göre çok yavaş çalışıyorlar. Setter-getter’a oranla 500–600 kat daha yavaşlar.
Sonuç
Kullandığım yöntemlere göre performans sıralaması şöyle oldu:
Setter-Getter > MapStruct > Selma > Orika > Dozer > Apache BeanUtils
Sonuç olarak obje dönüşüm işlemlerini mümkün mertebe en basit yöntem olan setter-getter ile yapmak performans açısından en iyisi olacaktır. Ancak bu yöntemde eğer objelerimizdeki field sayısı fazlaysa gereksiz kod yazımına sebep olacaktır.
Eğer hem çok gereksiz kod yazmamak hem de performanstan ödün vermemek isterseniz Selma ve MapStruct kullanabilirsiniz. Hem neredeyse setter-getter kadar hızlı hem de gereksiz kod yazmamış olursunuz.
Bunların dışında kalan Dozer ve BeanUtils’i ise performans kaybı çok yüksek seviyede olması sebebiyle zorunda kalmadıkça önermiyorum.
Başka bir yazıda görüşmek üzere.