Thursday, July 20, 2017

Java 9'da Çalışma Zamanından Önce Derleme


Java sınıfları javac derleyicisi ile derlenir ve sekizlik kodlar (=bytecode) içeren class uzantılı ikili bir dosya üretilir:

[centos@guru src]$javac -d ../bin com/example/animals/application/*.java

Bir uygulama çok sayıda Java sınıfından oluşur. Bu nedenle uygulamanın dağıtımını yapmak için tek bir dağıtım dosyasına dönüştürmek gerekir. Java Standard Edition'da dağıtım dosyası jar'dır.  Bir proje için jar dağıtım dosyasını üretmek için aynı isimli jar uygulamasını kullanıyoruz:

[centos@guru bin]$ jar -ecvf com.example.animals.application.AnimalApp app.jar .
added manifest
adding: com/(in = 0) (out= 0)(stored 0%)
adding: com/example/(in = 0) (out= 0)(stored 0%)
adding: com/example/animals/(in = 0) (out= 0)(stored 0%)
adding: com/example/animals/application/(in = 0) (out= 0)(stored 0%)
adding: com/example/animals/application/AnimalAppInJava7.class(in = 1322) (out= 788)(deflated 40%)
adding: com/example/animals/application/AnimalApp.class(in = 4253) (out= 1802)(deflated 57%)
adding: com/example/animals/domain/(in = 0) (out= 0)(stored 0%)
adding: com/example/animals/domain/Animal.class(in = 554) (out= 382)(deflated 31%)
adding: com/example/animals/domain/Cat.class(in = 1464) (out= 730)(deflated 50%)
adding: com/example/animals/domain/Pet.class(in = 218) (out= 167)(deflated 23%)
adding: com/example/animals/domain/Fish.class(in = 1565) (out= 762)(deflated 51%)
adding: com/example/animals/domain/Spider.class(in = 1007) (out= 554)(deflated 44%)

Java uygulamasını çalıştırmak için ise Java Sanal Makinasına ihtiyaç bulunuyor. java komutu ile Java Sanal Makinasına yazılımsal olarak erişmiş oluyoruz. jar olarak dağıtılmış bir Java uygulamasını basitçe aşağıdaki gibi bir komutla çalıştırabiliriz:

[centos@guru bin]$ java -cp app.jar com.example.animals.application.AnimalApp

Eğer jar oluşturulurken çalıştırılacak sınıfın hangi sınıf olduğu, başka bir deyişle uygulamanın giriş noktası tanımlanmışsa, uygulamayı daha da basit bir şekilde aşağıda verildiği gibi çalıştırabiliriz:

[centos@guru bin]$ java -jar app.jar

jar dağıtım dosyası üretilirken, jar komutuna uygulamanın giriş noktasının (çalıştırılacak sınıfın) hangisi olduğunu -e seçeneği ile veriyoruz.

JIT derleyici sekizlik kodları JSM’nin üzerinde çalıştığı platformun anlayacağı komutlara dönüştürür. Üstelik bunu yaparken devingen en iyileme de yapar. Bunun için uygulamanın basit bir kesitini (=profiling) çıkarır. Bu kesit bilgisine göre onlarca en iyileme tekniğinden hangilerini uygulayacağına karar verir. JIT'lenen kodlar elbette hızlı çalışır. Ancak bu işlemin bir zaman maliyeti vardır ve bu ödenen bedel özellikle uygulamanın açılış süresinin biraz uzamasına  ve uygulamada yer yer gecikmelerin yaşanmasına neden olur. JIT derleyici sınıf metotlarını, döngü bloklarını ve hataları ele aldığımız catch bloklarını derler.

Java Sanal Makinasının her türlü davranışını görünür kılabiliriz. Buna JIT derleyicinin davranışı da dahildir. JIT'lenen metotları görmek için uygulamayı aşağıdaki parametrelerle çalıştırmalısınız:

[centos@guru bin]$ java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -jar app.jar

Hatta JIT'lenen metotlarını makina dili karşılıklarını bile görebilirsiniz:

[centos@guru bin]$ java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -jar app.jar

Ancak yukarıdaki komutun ekrana JIT derleyicinin ürettiği kodun makina dili karşılığını yazabilmesi için özel bir kütüphaneye ihtiyacınız bulunuyor.

Oracle firmasının sağladığı Java Sanal Makina ürününün adı HotSpot'dur. Hotspot içinde iki farklı karakteristikte JIT derleyici bulunur: C1 ve C2. Java uygulamalarının başarımını ölçmek için farklı metrikler kullanarak ölçümler yapabiliriz. Açılış zamanı ve cevap süresi başarımı ölçmek için kullanılabilecek iki önemli ölçüttür. Masaüstü uygulamaları için açılış süresi ve web uygulamaları için ise cevap süresi daha önemlidir. Masaüstü uygulamaları, kullanıcı arayüzünü bir an önce kullanıcıya sunmalıdır. Örneğin, e-posta göndermek istiyorsunuz, bunun için favori e-posta istemcinizin  masaüstü simgesine tıkladınız. Pencerenin hemen açılmasını ve iletinizi bir an önce yazabilmeyi istersiniz. Nasıl olsa yazmaya başladığınızda, işlemci ile karşılaştırıldığında kaplumbağa hızında yazıyor olacaksınız. Yazılımın çok da hızlı çalışmaya ihtiyacı yok. Buna karşılık web uygulamalarında ise uygulama sunucusunun biraz geç açılmasına tahammül edebilirsiniz ama bir istek geldiğinde ona en hızlı sürede yanıt vermek ve cevabı dönebilmek istersiniz. İşte, C1 derleyicisi açılış süresini iyileştirmek ve C2 derleyicisi ise hızlı yanıt süresini iyileştirmek üzere en iyilenmiştir.

Ancak biz uygulamanın hem hızlı açılmasını hem de kısa sürede çok iş çıkarmasını isteriz! Java 7'de her iki JIT derleyiciyi (C1 ve C2) de katmanlı bir yapıda kullanan bir derleyici geldi: Katmanlı JIT Derleyici (=Tiered Compiler). Java 7'de bu derleyici varsayılan olarak kapalı geliyor. Açmak için uygulamayı -XX:+TieredCompilation seçeneği açık olacak şekilde çalıştırmanız gerekiyor:

[centos@guru bin]$ java -XX:+TieredCompilation -jar app.jar

Java 8'de ise katmanlı derleyici olgunlaştığı için varsayılan özellik olarak açık geliyor! Herhangi bir seçenek vermenize gerek bulunmuyor!

Peki, neden uygulama çalıştırılmadan önce class uzantılı dosyaları doğrudan uygulamanın üzerinde çalışacağı işlemcinin anlayacağı komutlara dönüştürmüyoruz? Genel olarak bu işlem uygulamanın birim zamanda iş çıkarma kapasitesini düşürecektir. Ancak bazı özel durumlarda uygulamanın açılış süresini iyileştirme potansiyeli bulunuyor. Java 9'da çalışma zamanından önce derleme (=ahead of time compilation, AOT) geldi. Başlangıçta, bu özelliğin sadece 64-bitlik Linux sistemlerde çalıştığını belirtmem gerekiyor. 

Çalışma zamanından önce derleyici Java Geliştirme Kiti içinden çıkan bir araç olarak geliyor: jaotc. jaotc ile bir sınıfı, bir dağıtım dosyası içindeki sınıfları ve bir modül içindeki sınıfları derleyebiliriz. Derlendiğinde ise so uzantılı paylaşımlı kütüphane dosyası oluşturuluyor.

Önce bir sınıfı jaotc ile derleyelim:

[centos@guru bin]$ jaotc --compile-for-tiered -J-XX:-UseAOT -J-Xmx2g  --info com/example/animals/application/AnimalApp.class --output ./libanimalapp.so
Compiling ./libanimalapp...
1 classes found (59 ms)
4 methods total, 4 methods to compile (7 ms)
Compiling with 2 threads
.
4 methods compiled, 0 methods failed (852 ms)
Parsing compiled code (4 ms)
Processing metadata (28 ms)
Preparing stubs binary (1 ms)
Preparing compiled binary (0 ms)
Creating binary: ./libanimalapp.o (15 ms)
Creating shared library: ./libanimalapp.so (30 ms)
Total time: 1863 ms

Şimdi bir jar dağıtım dosyası içindeki sınıfları jaotc ile derleyelim:

[centos@guru bin]$ jaotc --compile-for-tiered -J-XX:-UseAOT -J-Xmx2g  --info --jar app.jar --output ./libanimals.so
Compiling ./libanimals...
7 classes found (97 ms)
31 methods total, 27 methods to compile (11 ms)
Compiling with 2 threads
.
27 methods compiled, 0 methods failed (2214 ms)
Parsing compiled code (4 ms)
Processing metadata (54 ms)
Preparing stubs binary (1 ms)
Preparing compiled binary (1 ms)
Creating binary: ./libanimals.o (20 ms)
Creating shared library: ./libanimals.so (20 ms)
Total time: 3211 ms

Son olarak ise java.sql modülü içindeki sınıfları jaotc ile derleyelim:

[centos@guru ~]$ jaotc --compile-for-tiered -J-XX:-UseAOT -J-Xmx2g --info --module java.sql --output ./libjava.sql-tiered.so
Compiling ./libjava.sql-tiered...
81 classes found (113 ms)
1311 methods total, 323 methods to compile (101 ms)
Compiling with 2 threads
....
323 methods compiled, 0 methods failed (6650 ms)
Parsing compiled code (26 ms)
Processing metadata (226 ms)
Preparing stubs binary (1 ms)
Preparing compiled binary (3 ms)
Creating binary: ./libjava.sql-tiered.o (131 ms)
Creating shared library: ./libjava.sql-tiered.so (208 ms)
Total time: 8365 ms

Uygulamaları çalıştırırken Java Sanal Makinasının, so kütüphanesinde verilen metodları kullanmasını ise aşağıdaki örneklerdeki gösterilen seçenekleri vererek sağlıyoruz:

[centos@guru bin]$ java -XX:AOTLibrary=./libanimalapp.so -XX:+UnlockDiagnosticVMOptions -XX:+PrintAOT -XX:+UseAOTStrictLoading -XX:+UseAOT -jar app.jar com.example.animals.application.AnimalApp
     15    1     loaded    ./libanimalapp.so  aot library
    172    1     aot[ 1]   com.example.animals.application.AnimalApp.main([Ljava/lang/String;)V
    172    2     aot[ 1]   com.example.animals.application.AnimalApp.<init>()V
    172    3     aot[ 1]   com.example.animals.application.AnimalApp.lambda$main$1(Lcom/example/animals/domain/Animal;)Z
    172    4     aot[ 1]   com.example.animals.application.AnimalApp.lambda$main$0(Ljava/lang/Class;Lcom/example/animals/domain/Animal;)V

[centos@guru bin]$ java -XX:AOTLibrary=./libanimals.so -XX:+UnlockDiagnosticVMOptions -XX:+PrintAOT -XX:+UseAOTStrictLoading -XX:+UseAOT -jar app.jar com.example.animals.application.AnimalApp
     11    1     loaded    ./libanimals.so  aot library
    144    1     aot[ 1]   com.example.animals.application.AnimalApp.<init>()V
    144    2     aot[ 1]   com.example.animals.application.AnimalApp.lambda$main$1(Lcom/example/animals/domain/Animal;)Z
    144    3     aot[ 1]   com.example.animals.application.AnimalApp.lambda$main$0(Ljava/lang/Class;Lcom/example/animals/domain/Animal;)V
    144    4     aot[ 1]   com.example.animals.application.AnimalApp.main([Ljava/lang/String;)V
    181    5     aot[ 1]   com.example.animals.domain.Animal.getLegs()I
    181    6     aot[ 1]   com.example.animals.domain.Animal.<init>(I)V
    181    7     aot[ 1]   com.example.animals.domain.Animal.walk()V
    181    8     aot[ 1]   com.example.animals.domain.Cat.<init>(Ljava/lang/String;)V
    181    9     aot[ 1]   com.example.animals.domain.Cat.<init>()V
    181   10     aot[ 1]   com.example.animals.domain.Cat.play()V
    181   11     aot[ 1]   com.example.animals.domain.Cat.eat()V
    181   12     aot[ 1]   com.example.animals.domain.Cat.toString()Ljava/lang/String;
    181   13     aot[ 1]   com.example.animals.domain.Cat.getName()Ljava/lang/String;
    181   14     aot[ 1]   com.example.animals.domain.Cat.setName(Ljava/lang/String;)V
    181   15     aot[ 1]   com.example.animals.domain.Spider.<init>()V
    181   16     aot[ 1]   com.example.animals.domain.Spider.eat()V
    181   17     aot[ 1]   com.example.animals.domain.Spider.toString()Ljava/lang/String;
    181   18     aot[ 1]   com.example.animals.domain.Fish.<init>(Ljava/lang/String;)V
    181   19     aot[ 1]   com.example.animals.domain.Fish.<init>()V
    181   20     aot[ 1]   com.example.animals.domain.Fish.play()V
    181   21     aot[ 1]   com.example.animals.domain.Fish.toString()Ljava/lang/String;
    181   22     aot[ 1]   com.example.animals.domain.Fish.eat()V
    181   23     aot[ 1]   com.example.animals.domain.Fish.getName()Ljava/lang/String;
    181   24     aot[ 1]   com.example.animals.domain.Fish.setName(Ljava/lang/String;)V
    181   25     aot[ 1]   com.example.animals.domain.Fish.walk()V

jaotc derlenen ve çalıştırılan kod parçaları başlangıçta yorumlamalı çalıştırmaya göre kesinlikle daha hızlı çalışacaktır. Üstelik başlangıç bu kodun JIT'lenmesi için zaman kaybedilmediği için uygulamamız potansiyel olarak daha hızlı açılacak ve hızlı çalışacaktır. AOT kullanmadan önceki ve sonraki başarımı mutlaka ölçmelisiniz! 

No comments:

Post a Comment