Ticker

6/random/ticker-posts

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.

Java'da ForkJoinPool kullanımı
Java'da ForkJoinPool kullanımı

 

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

 





Yorum Gönder

0 Yorumlar