ÖNEMLİ : Kendim için aldığım notlar. Umarım size de bir faydası olur. Kullanılan her bir makale referans olarak eklenmiştir. Rice Üniversitesi’nin hazırladığı eğitsel bir Framework olan PCDP bu ve sonraki bölümlerde kullanılacaktır. async ve finish notasyonları bu Framework’de yer almaktadır.

Java Paralel Programlama Serisi


  1. Java Paralel Programlama - Bölüm 1
  2. Java Paralel Programlama - Bölüm 2
  3. Java Paralel Programlama - Bölüm 3
  4. Java Paralel Programlama - Bölüm 4

Genel Bakış

Bir önceki bölümde hesaplama grafiklerini görmüştük. Şimdi ise gerçek çok çekirdekli bilgisayarlarda nasıl haritalandıklarını görebiliriz. Bir önceki bölümde resmettiğimiz hesaplama grafiğini tekrar göz önüne getirelim ve her bir işleme bazı yürütme zamanlarını verelim. S6 dışında bütün işlemlere 1 birim çalışma zamanı verdiğimizi varsayalım. S6 ise 10 olsun.


Java hesaplama grafiği(java computation graph)

Burada ilgilendiğimiz şey, P kadar işlemci olduğunda T yürütme zamanını bilmek istiyoruz. Yani, P işlemcili çok çekirdekli bir makinemiz varsa: Örn; 2, 4, 8, 16 çekirdekli, verilen bir hesaplama grafiği için hangi işlem zamanını alabiliriz?

  • Tp = P işlemcilerinde yürütme süresidir (Execution time on P processors)

Hesaplama grafiğindeki bu adımların aslında işlemcilerde nasıl zamanlandığını düşünecek olursak, aşağıdaki gibi bir gösterim yapabiliriz. Fakat bunu bilgisayarlar arka planda kendi yaptıkları için endişelenmemizi gerektirecek bir durum yoktur. Hesaplama grafiğindeki adımları işlemciler arasında programlamak, çalışma zamanı yazılımı ve donanımının işidir.

2 işlemcili bir durumu ele aldığımızı varsayalım ve bu adımların nasıl programlanabileceğini görelim.


Java hesaplama grafiğinin(java computation graph) işlemciler üzerinde gösterimi

Dolayısıyla, ne olursa olsun S1‘in ilk önce gerçekleştirilmesi gerektiğini görüyoruz, çünkü grafiğin asıl amacı, sıralama ilişkilerini gösterir ve S1 işini bitirene kadar başka hiçbir şey çalışmaz. S1‘i rastgele olarak P0‘da başlattığımızı varsayalım. Sonrasında S2 ve S4 ve S6 hepsi çalıştırılabilir. Ama iki işlemci olduğu için 3 işlemden 2‘sini seçmemiz gerekli.

Farzedelim ki, S2 ve S4‘ü seçtik. P0, S2‘yi, P1 ise S4‘ü çalıştırdı. Şimdi S2 ve S4 birbirleriyle paralel olarak çalışıyorlar. Akabinde, P0, S3‘ü, P1 ise S5‘i hesaplama grafiğindeki gibi çalıştırmaya devam eder. Şimdi, bundan sonra S7‘yi uygulayamıyoruz çünkü S6 hâlâ bitirilmeyi bekliyor.

Her iki işlemci de boşa çıktığı için ikisinden birinde S6 çalıştırılabilir. Farzedelim ki bu görevi P0 aldı. Artık son işlem olan S7‘ye geçebiliriz. Bunu da S6‘da olduğu gibi boşta olan herhangi bir işlemci alabilir. Yine P0‘ın aldığını varsayalım. Yukarıda görüldüğü üzere işlemciler üzerinde ilgili planlama yapıldı. Şimdi ise bu işlemcilerin çalışma zamanlarını üzerinde tekrar düşünebiliriz.


Java hesaplama grafiğinin(java computation graph) işlemciler üzerinde gösterimi

Planlama bu şekilde olduğunda 2 işlemcideki çalışma süresini 14(yani T2 = 14) olarak hesapladık. Dikkat edilecek olursa 2 adet de IDLE(atıl) slot göze çarpmaktadır. Yani bu slotların boşta olduğunu, yapacak bir işlerinin olmadığını göstermektedir. Ancak, grafiği programlamanın tek yolu bu değildir. Başka bir yaklaşım da izleyebiliriz. Çünkü S6 bir darboğaz oluşturuyor ve P1 işlemcisinin belirli durumlarda boşta(IDLE) kalmasına neden oluyor. Dolayısıyla, şimdi ikinci planlamadaki hedefimiz S6‘yı mümkün olan en kısa sürede çalıştırmak olacaktır.


Java hesaplama grafiğinin(java computation graph) işlemciler üzerinde gösterimi

Bu planlama örneğinde S1‘i yine ilk olarak başlatmak zorundayız. Tabii S1 başladığında P1 bir öncekinde olduğu gibi başlangıçta boş kalır. S1 işlemi bittiğinde, P0‘da S6‘yı önceliklendirebiliriz. Böylece S6, 10 birim zaman boyunca P0 üzerinde çalışırken, P1‘de S2, S3, S4, S5 işlemlerini rahatlıkla ele alabiliriz. Sıra önemli değil çünkü bunlar, hangi sırayla yapılırsa yapsınlar toplamda dört iş birimi ile çalışırlar. Yani 10 birim zamana sahip S6‘yı düşünürsek, işlemlerini S6‘dan önce bitirecekleri kesindir. S6‘da işlemini sonlandırdıktan sonra S7‘yi işleme alabiliriz. S7 herhangi bir işlemci de başlayabilir. Varsayalım ki P0‘da başladı. Peki, şimdi bu planlama için yürütme süresi ne oldu?. Burada S1‘in 1, S6‘nın 10 ve S7‘nin 1 birim süresi olduğu için toplamda 12 birim çalışma süresi olduğunu görüyoruz.

Dolayısıyla, bir öncekine kıyasla daha küçük bir yürütme zamanımız var. Bu nedenle, P kadar işlemcide yürütme süresinin bu tanımı aslında tasarlanan bu plana bağlıdır. Ancak bu yürütme süresi hakkında belirleyebileceğimiz belirli özellikler vardır. Birincisi, P –> 1‘e eşit olsaydı ne olurdu? Eğer sadece 1 işlemcimiz olsaydı, T1'in --> WORK'e eşit olduğunu iddia edebilirdik. Bunu neden söylüyoruz? Çünkü bir işlemcide çalışıyorsanız, temel olarak bu işlemcinin, hesaplama grafiğindeki tüm işleri yapması gerekir. Bu işlem, tüm yürütme zamanlarını eklediğinizde elde ettiğiniz şeydir.

  • P = 1 —> T1 = WORK

Örneği düşünecek olursak tüm çalışma zamanlarının toplamı;

1
2
3
S1 + S2 + S3 + S4 + S5 + S6 + S7 = ?
1  + 1  + 1  + 1  + 1  + 10 + 1  = 16
T1 = 16

Düşünebileceğimiz diğer bir şey ise, pratikte olmasa da, sonsuz sayıda işlemcimiz olsaydı ne olurdu? Cevap daha önce öğrendiğimiz, yani hesaplama grafiğindeki en uzun yolun uzunluğunu temsil eden SPAN olurdu. Çünkü yeterli işlemcimiz varsa, en uzun yolda olmak ve kendinden sonra gelecek işlemleri beklemek dışında bir adım dahi beklemenin bir sebebi yoktur.

Şekil 1’e bakacak olursak, başta hesaplama grafiğini çizdiğimizde 3 tane paralel çalışan işlemi düşünmüştük. İşlemin uzunluğu da haliyle bu dallanmalardan en son hangisi biterse ona göre belirlenir. Çünkü diğer işlemler önce bitse de, en son join işlemi yapılacağı zaman birbirlerini beklemek zorundalar. Paralel çalışacak yeterli sayıda işlemcimiz olacağı için 3 dallanmaya da yeterli sayıda işlemci olacaktır. S3, S2‘den sonra gelen bir işlem, S5 de S4‘den sonra gelen bir işlem olduğu için onlar yeni bir işlemci de çalışmaz.

Not:


Yani sonsuz sayıda da işlemciniz olsa hesaplama grafiğine göre en fazla 3 tane dallanmanız olacaktır. Kısacası en son biten işlem size SPAN değerini de verecektir. Bu durumda SPAN 1+10+1 = 12 olur. O halde, herhangi bir P sayıda işlemciye bakıldığında, aşağıdaki aralıkta olmamız gerektiğini biliyoruz;

  • T1 = WORK = 16

  • T = SPAN = 12

  • T ≤ Tp ≤ T1

Paralel programlar hakkında konuşurken çok ilginç bir diğer kavram ise hızlanmadır(SPEEDUP). Bu yüzden, paralelliğin asıl amacı, donanım üreticilerinin bize verdiği tüm bu çekirdeklerle programınızın daha hızlı çalışmasını sağlamaktır.

  • SPEEDUP = T1 / TP

Öyleyse bunu düşünelim. T1 sıralı yürütme süresidir. TP, P işlemcide aldığımız yürütme süresidir. Ve bu oran, paralel versiyonun ne kadar hızlı çalışabildiğinin faktörü olacaktır. Bu yüzden, hızlanmanın P‘ye küçük eşit olması gerektiğini görebiliyoruz.

  • SPEEDUP ≤ P

Speedup(P) must be ≤ the number of processors P.

Aynı şekilde, SPEEDUP aşağıdaki gibi de olmalı;

  • SPEEDUP ≤ WORK/SPAN = IDEAL PARALLELISM

Speedup(P) must be ≤ the ideal parallelism, WORK/SPAN.

Dolayısıyla, hızlanmanın elbette kaç işlemcinin mevcut olduğuna bağlı olduğunu ve aynı zamanda “ideal paralellik” olan hesaplama grafiğinin bu gerçekten önemli özelliği ile de sınırlandığını görüyoruz. Paralel algoritmalarda hedefimiz, sahip olduğunuz işlemci sayısından çok daha büyük olan ideal paralelliğe sahip hesaplama grafikleri oluşturmaktır, böylece, bu paralel programı çok sayıda işlemcide çalıştırma esnekliğine sahip olursunuz.

  • IDEAL PARALLELISM ≥ P

Referanslar :

  1. What is a Data Race?
  2. Asynchronous method invocation
  3. Analysis of parallel algorithms
  4. Class RecursiveAction
  5. Class RecursiveTask
  6. Class ForkJoinPool
  7. Package java.util.stream
  8. Interface Stream
  9. Fork/Join
  10. Java VisualVM
  11. Parallel Programming in Java
  12. PCDP parallel programming framework
  13. Şekil 1,2,3,4 - lucidchart ile hazırlanmıştır.