Java’da Performans Arttırıcı Yollar - Bölüm 1
6 Ocak 2006 tarihli, Java, Programlama köşesine ait yazı.
Glen McCluskey’nin 1999′da yazdığı “Thirty Ways to Improve the Performance of Your Java Programs” yazısının çevirisidir. Hem Java’yla büyük projelerde çalışanların, hem de Java’nın biraz derinlerine inmek isteyenlerin okumasını şiddetle tavsiye ediyorum. 4 ya da 5 bölüm halinde yayınlayacağım yazının ilk bölümü Java’da nesneler ve metodlarla ilgili.
Yazı ve Yazar Hakkında
- Websitesi: http://www.glenmccl.com/jperf/
- Yazının orijinali: ftp://ftp.glenmccl.com/pub/free/jperf.pdf
- Türkçe’ye çeviren: Onur Küçüktunç
İçindekiler
- 1. Giriş
- 2. Nesneler
- 3. Metodlar
- 4. String’ler
- 5. Input/Output
- 6. Kütüphaneler (Library)
- 7. Verimli Disk ve Bellek Kullanımı
1. Giriş
Java programlama dili her geçen gün gelişen ve performansı artan bir dildir. Performans denilince düşünülmesi gereken konular programınızın ne kadar hızlı çalıştığı ve ne kadar az bellek ve disk alanı harcadığıdır. Tabii ki performans, uygulama geliştiricilerinin tek düşünmeleri gereken nokta değildir, çünkü kodun kalitesi, okunurluğu gibi etkenler de en az performans kadar önemli konulardır. Yazılanların daha iyi anlaşılması yönünden bu noktanın önemini belirtmek gerekiyor. Yazarın kullandığı derleyici, testleri yaptığı ortam ve performans analizini nasıl yaptığı hakkında detaylı bilgiyi yazının orijinalinden okuyabilirsiniz.
Not: Tümüyle ve manasızca Türkçe’ye çevirmeye uğraşmayacağım için anlaşılabilir terimleri İngilizce bırakmayı uygun görüyorum.
2. Nesneler
Bu bölümde constructor ve initialization hakkında birkaç performans sorunu ele alınacak.
2.1 Default Constructor
Java’da, eğer bir nesnenin constructor metodu yazılmadıysa, compiler tarafından default constructor oluşturulur. Mesela şöyle bir class tanımladığınızda:
public class cls_default
java’da her nesne aynı zamanda bir Object olduğundan aslında şöyle anlaşılır:
public class cls_default extends java.lang.Object
Bir constructor tanımlasanız bile yeni bir obje oluşturduğunuzda java.lang.Object class’ının constructor metodu çağırılır.
2.2 Constructor Hiyerarşisi
Bir önceki maddeyi şöyle genelleyebiliriz. Düşünün ki şöyle bir class hiyerarşiniz var:
class A {…}
class B extends A {…}
class C extends B {…}
Şimdi yeni bir C objesi yaratırken constructor metodunuz sırasıyla önce B constructor’ına, sonra A constructor’ına, daha sonra da java.lang.Object class’ının constructor’ına gidecek, tüm bu metodlar çalıştırıldıktan sonra objenin kendi constructor’ı tamamlanacaktır. Bu örnek, method inlining başlığında anlatacağımız konunun önemini anlatmaktadır.
2.3 Initialization
new kullanarak oluşturduğunuz her instance, yani obje için tanımlı tüm değişkenler ayrı ayrı yaratılır. Buna karşın static tanımlı değişkenler yalnızca bir kez tanımlanır ve her obje tarafından ortak kullanılır.
Gerektiğinde static değişkenler kullanmak yazıdaki örnek için neredeyse 10 kat zaman kazandırmış, bunun yanında bir sürü bellek alanının gereksiz yere kullanılmasını engellemiş. Genel olarak factoring out yapmak, yani her obje için özel olmayan değişkenleri ve metodları static olarak tanımlayıp tüm objelerin ortak olarak kullanmasını sağlamak performansı artırıcı önemli bir etkendir.
2.4 Geri Dönüşümlü Objeler
Düşünün ki bir linked list oluşturuyorsunuz ve node yapınız şu şekilde:
class Node {
Object data; // data
Node link; // link to next node in the list
}
ve birçok node için allocation ve garbage collecting konusunda endişelisiniz. Bu durumda uygulayabileceğiniz bir teknik var. Her yeni node için new kullanarak yeniden oluşturmak yerine freelist adında bir değişken kullanıp recycling yapabilirsiniz. Mesela noderef adında bir node referansı artık kullanılmıyor. Bu node’a olan tüm referansları koparmak yerine
noderef.link = freelist;
freelist = noderef;
diyerek kullanılmayan node’unuzu freelist‘in başına ekleyebilirsiniz. Yeni bir node yaratmanız gerektiğinde ise freelist‘in null olup olmadığını kontrol edip şu şekilde bir node atayabilirsiniz:
noderef = freelist;
freelist = noderef.link;
Tabii ki bu tekniğin thread-safe olarak çalışabilmesi için listeyi güncelleyen synchronized metodların yazılması gereklidir. Bu yaklaşım en çok, çok sayıda nesneyi yönetmeniz gerektiğinde mantıklı olacaktır.
3. Metodlar
Bu bölümde metod kullanımında performansı etkileyen noktalardan bazıları tartışılacaktır. Kontrolün bir metoda geçmesi, parametre gönderilmesi, geri dönen değerin gönderilmesi ve çağıran metodun yerel değişkenlerinin stack’te nasıl tutulduğuyla ilgili birkaç performans sorunu ve çözümleri tanıtılacak.
3.1 Inlining
Inlining aslında bazı derleyiciler tarafından otomatik yapılan, bazen de sizin elle yapmanız gereken, performansı artırıcı bir özelliktir. Tabii ki code reuse kavramıyla ne kadar uyumludur tartışılır, ancak aşağıdaki örnekte görüldüğü üzere biraz işe yaradığı belli. Düşünün ki verilen iki sayının küçük olanını döndüren şöyle bir metodunuz var:
public static int min(int a, int b) {
return (a<b?a:b);
}
Java ile hangi tekniğin daha hızlı çalıştığını bulmak için şöyle bir kod yazıyoruz:
final int N = 10000000;
int a = 5;
int b = 17;
int c;
/* metod çağırarak */
for(int i=1;i<=N; i++)
c = min(a, b);
/* aynı metodu inline ederek */
for(int i=1;i<=N; i++)
c=(a<b?a:b);
Sonuç göstermiş ki inlining kullanılarak yapılan işlem, metod çağırılarak yapılana göre ~2 kat hızlı çalışmış.
3.2 Final Metodlar
Metodları final tanımlamak derleyiciye inlining için yardımcı olur. Böylece hiçbir subclass bu metodu override edemez. Aynı şekilde bir nesne de final tanımlanabilir, ki bu nesnenin subclass’larının olamayacağını gösterir.
public final void f() {…}
public final class A {…}
Bir metodu veya nesneyi final tanımlamaktaki amaç şu şekilde açıklanabilir. Düşünün ki final tanımlanmış bir A objesine bir referans tanımlayıp f() fonksiyonunu çağırdınız:
A aref = new A();
aref.f();
Böyle olunca ben herhangi bir A veya subclass’ından yaratılmış objenin f() fonksiyonuna değil, direkt A’nın f() fonksiyonunu çağırmaktayım. Bu ise inlining işlemini compiler için kolaylaştırmaktadır.
3.3 Synchronized Metodlar
Thread programlamada kullanılan synchronized metodlar non-synchronized metodlara göre daha yavaştır. Mesela biri synchronized diğeri ise normal iki metod tanımlayalım ve loop içerisinde çağıralım:
public void meth1() {}
public synchronized void meth2() {}
final int N = 1000000;
/* non-synchronized */
for(int i=1;i<=N; i++)
x.meth1();
/* synchronized */
for(int i=1;i<=N; i++)
x.meth2();
}
Synchronized metod normal metoda göre 2 kat zamanda tamamlanmıştır. Bazı durumlarda bu performans kaybı önemli olmaktadır.
3.4 Inner Class
Bu bölüm bir inner class’tan üst class’ın private metodlarına nasıl erişildiği hakkında yazılmış. Ancak performansla ilgili bir bilgi bulamadığımdan çevirmemeyi uygun gördüm. İsterseniz orijinal metinin yine 3.4 bölümünden okuyabilirsiniz.
Yorumlar - Başa Dön
19 Ocak 2006
üzerinde emek çekildiği belli olan başarılı bir yazı dizisi olmuş.
teşekkürler…
11 Kasım 2006
inlining olayını öğrenmek gerçekten güzel oldu. henüz bundan yararlanmayı gerektirecek büyüklükte programlar yazamasam da, kodun daha performanslı yazıldığını bilmek güzel…
Teşekkürler
22 Ocak 2007
büyük bir projeye yeni katıldım, böyle bir çeviriyi yaptığınız için teşekkürler.
17 Şubat 2009
Güzel bir çeviri olmuş. Teşekkürler