15 Şubat 2021 Pazartesi

Java'da BÖL - PARÇALA - YÖNET (Fork / Join Pool Kullanımı)

Merhabalar. Yoğun işlem yüklü hesaplamalarımızı paralel olarak çalıştırarak süreden kazanmak istemez miyiz? Bu yazımda Java'da işlem yükünü işlemcide birden fazla çekirdeğe yayarak paralel çalıştırma altyapısı olan Fork / Join Pool kullanımından bahsedeceğim. 

Fork / Join altyapısı, tanımlanan bir işi asenkron olarak çalıştırılabilecek daha küçük alt işlere böler (Fork). Yeterince böldüğüne kanaat getirdiğinde alt işleri asenkron olarak çalıştırır ve sonuçlarını birleştire birleştire tanımlı işi tamamlar. Eğer alt işler void dönüyor ve birleştirilecek birşey yok ise, sadece tüm alt işlemlerin bitmesini bekler (Join). Her alt işlem için yeni thread oluşturmaz. Tanımlanan ForkJoinPool nesnesi, kendisine bildirilen çekirdek sayısını optimum kullanacak şekilde alt işleri bir sıraya koyar ve threadlere dağıtır. 

 

Çok basit bir senkron for döngüsünü Fork / Join yapısını kullanır hale getirerek kazandığımız süreyi hep beraber görelim.

Öncelikle senkron çalışan kod aşağıdaki gibidir. For döngüsü içinde pahalı bir işlem olduğunu simüle edebilmek için bir miktar hesaplama kodu ekledim. 

     public static void main(String[] args) {
      
        long baslangic = System.currentTimeMillis();
       
        Map<Integer, Double> sonucMap = new ConcurrentHashMap<>();
       
        for(int i = 0; i < 1000000; i++) {
       
            double kare = Math.sqrt(i);
            double tan = Math.tan(kare);
            double cos = Math.cos(tan);
           
            double radians = Math.toRadians(cos);
            double degrees = Math.toDegrees(radians);
           
            double exp = Math.exp(degrees);
           
            double log10 = Math.log10(exp);
           
            double atan2 = Math.atan2(log10, 5);
           
            double sqrt = Math.sqrt(atan2*atan2 + log10 * log10);
           
            double square = sqrt * sqrt;
           
            sonucMap.put(i, square);
        }
       
        System.out.println("süre " + (System.currentTimeMillis() - baslangic) + " ms");
       
    }

Bu kodu makinamda birden fazla kez çalıştırdığımda süreler yaklaşık 470 - 500 ms arası oluyor.

Şimdi de Fork / Join yapısını kullanan sınıfı görelim.


public class BolParcalaYonet {

    public static class Hesaplama extends RecursiveAction {
       
        int baslangic;
        int bitis;
        Map<Integer, Double> sonucMap;
       
        private static final int THRESHOLD = 100000;

        public Hesaplama(int baslangic, int bitis, Map<Integer, Double> sonucMap) {
            this.baslangic = baslangic;
            this.bitis = bitis;
            this.sonucMap = sonucMap;
        }

        @Override
        protected void compute() {
            if (bitis - baslangic > THRESHOLD) {
                ForkJoinTask.invokeAll(altIslereBol());
            } else {
               hesapla();
            }
        }

        private void hesapla() {
             
            for(int i = baslangic; i <= bitis; i++) {
           
                double kare = Math.sqrt(i);
                double tan = Math.tan(kare);
                double cos = Math.cos(tan);
               
                double radians = Math.toRadians(cos);
                double degrees = Math.toDegrees(radians);
               
                double exp = Math.exp(degrees);
               
                double log10 = Math.log10(exp);
               
                double atan2 = Math.atan2(log10, 5);
               
                double sqrt = Math.sqrt(atan2*atan2 + log10 * log10);
               
                double square = sqrt * sqrt;
               
                sonucMap.put(i, square);
               
            }
           
        }

        private List<Hesaplama> altIslereBol() {
            List<Hesaplama> subtasks = new ArrayList<>();

            int orta = baslangic + (bitis - baslangic) / 2;
           
           // System.out.println("bas orta bitis " + baslangic + " " + orta + " " + bitis);
           
            subtasks.add(new Hesaplama(baslangic, orta,sonucMap));
            subtasks.add(new Hesaplama(orta + 1, bitis, sonucMap));

            return subtasks;
        }
       
    }
    
    public static void main(String[] args) {

        ForkJoinPool commonPool = ForkJoinPool.commonPool();

        long baslangic = System.currentTimeMillis();
       
        Map<Integer, Double> sonucMap = new ConcurrentHashMap<>();

        Hesaplama hesaplama = new Hesaplama(0, 1000000, sonucMap);
       
        commonPool.invoke(hesaplama);
              
        System.out.println("süre " + (System.currentTimeMillis() - baslangic) + " ms");
       
    }
}


Aynı hesaplamayı bu şekilde birden fazla kez çalıştırdığımda süreler yaklaşık 160 - 180 ms arasında oluyor. Çok büyük bir performans kazanımı olduğu görülüyor.

Burada belirleyeceğimiz eşik (treshold) değerine dikkat etmmemiz gerekiyor. Eğer üzerinde çalıştığımız işlemcinin çekirdek sayısına oranla çok fazla alt işlere bölme ile sonuçlanacak bir eşik değer belirlenirse performans kazanmayı beklerken tam tersi performans kaybıyla da karşılaşabiliriz.

altIslereBol() metodunda yorumda bıraktığımız println satırını açarsak, hangi başlangıç bitiş alt işlerine ayırdığını konsolda daha net şekilde görüp anlayabiliriz.

>

bas orta bitis 0 500000 1000000
bas orta bitis 0 250000 500000
bas orta bitis 500001 750000 1000000
bas orta bitis 0 125000 250000
bas orta bitis 500001 625000 750000
bas orta bitis 750001 875000 1000000
bas orta bitis 0 62500 125000
bas orta bitis 625001 687500 750000
bas orta bitis 500001 562500 625000
bas orta bitis 750001 812500 875000
bas orta bitis 875001 937500 1000000
bas orta bitis 250001 375000 500000
bas orta bitis 250001 312500 375000
bas orta bitis 125001 187500 250000
bas orta bitis 375001 437500 500000
süre 179 ms

 





9 Şubat 2021 Salı

Java Sürprizleri

Merhabalar. Java kodlarken hiç ummadığınız hatalarla saatler harcadığınız sizin de olmuştur. Onca tecrübenize rağmen Java dilinin geliştiricilerinin size hazırladığı sürprizlerle karşılaşırsınız ara sıra. Bu yazımda karşılaştığım sıradışı Java özelliklerinden bahsedeceğim.

 


File exist() Metodu Aynı Dosya İçin Linux ve Windows'da Farklı Sonuç Dönebilir

Elimizde ADANA.JPG isimli bir dosya olsun. Kodumuzun bir yerinde 

        File memleket = new File("adana.jpg");

        System.out.println("memleket exist() " + memleket.exists());

satırları bulunsun. Sizce konsola ne yazacaktır? Linux oramında iseniz false Windows ortamında iseniz true yazacaktır. exist() metodu dosyanın olup olmadığının cevabını işletim sistemine sorarak dönmektedir. Windows ise dosya adlarının harflerinin büyük ya da küçük olmasıyla ilgilenmeden cevap dönerken Linux sistemler kesinlikle aynı büyük küçük harflere sahip dosya olup olmadığına bakmaktadır.

 PriorityQueue İteratörü Sıralı Dönmek Zorunda Değil

 PriorityQueue<SinavSonucu> sinavSonuclari = new PriorityQueue<>(); 

şeklinde bir PriorityQueue nesnemiz olsun. PriorityQueue nesnesi genelde hep içindeki elemanların en önceliklisini alıp aynı zamanda queue içinden silinmesi ile sonuçlanan poll() metodu ile kullanılır. Queue içindeki nesneleri silmeden dolaşmak istediğimizde de diğer Collection nesnelerinde olduğu gibi iteratorünü kullanarak dolaşırız.

        for(SinavSonucu sonuc : sinavSonuclari){

            // ....

        

Fakat burada dikkat etmemiz gereken bir nokta var. İçgüdüsel olarak bu for içinde de elemanları öncelik sırasına göre dolaşacağımızı varsayabiliriz. Fakat bu doğru değil. PriorityQueue nesnelerinin iterator() metodu içindeki nesneleri poll() metodunda olduğu gibi öncelik sırasına göre vermeyi garanti etmiyor.

 

Double.MIN_VALUE Sıfırdan Büyüktür

Java'da nümerik değerlerin MAX ve MİN değerlerine ulaşmamız gereken durumlar olabilir. Mesela, Integer.MAX_VALUE, Integer.MIN_VALUE, veya konumuz olan Double.MIN_VALUE. Sizce aşağıdaki kodun konsol çıktısı ne olacaktır?

        System.out.println(Double.MIN_VALUE); 

        if(Double.MIN_VALUE > 0) {
           System.out.println("Hakkaten sıfırdan byükmüş");
        }
        

        System.out.println(Integer.MIN_VALUE);
        

        if(Integer.MIN_VALUE > 0) {
                  System.out.println("integer min value sıfırdan büyük"); 

        }

> 4.9E-324
Hakkaten sıfırdan byükmüş
-2147483648

Integer.MIN_VALUE negatif en küçük integer değeri verirken Double.MIN_VALUE ise negatif en küçük double değeri yerine sıfırdan büyük en küçük Double değerini veriyor. Dikkat etmek lazım.



JTS CBS Kütüphanesi Rehberi 1 - Geometri Modeli

Merhabalar. 2019 yılı başından beri işimde bir CBS (coğrafi bilgi sistemi) ürünü üzerinde çalışıyorum. Java dünyasında harita üzerinde geli...