Paralel Programlama Ortamları Elis Soylu1, M. Akçay2 1 2 Eskişehir Osmangazi Üniversitesi, Fen Edebiyat Fakültesi Matematik- Bilgisayar Bölümü, Eskişehir Dumlupınar Üniversitesi, Mühendislik Fakültesi, Bilgisayar Mühendisliği Bölümü,Kütahya [email protected], [email protected] Özet: Bu çalışmada paralel programlama mantığından bahsedilip, bu ortamların karakteristik özellikleri üzerinde durulacaktır. Okunabilirlik, kısalık, hata kontrolü, sınıf – metot yapıları, etkinlik, modülerlik ve polimorfizm gibi özellikler incelenecektir. MPI (Message Passing Interface), Java Thread, CUDA (Compute Unified Device Architecture), OpenMP, PThread ve Haskell dilleri ile paralel matris çarpımı algoritması üzerinde inceleme yapılacaktır. Anahtar Sözcükler: Paralel Programlama, Matris Çarpımı, MPI, Java Thread, CUDA, OpenMP, PThread, Haskell. Parallel Programming Platforms Abstract: In this paper, parallel programming’s logic is mentioned and these platforms’ characteristical properties will be given. Readability, brevity, error checking, class-method structure, effiency, modularity and polymorphism will be investigated. Parallel matrix multiplication algorithm will be investigated with MPI (Message Passing Interface), Java Thread, CUDA (Compute Unified Device Architecture), OpenMP, PThreads and Haskell. Keywords: Parallel Programming, Matrix Multiplication, MPI, Java Thread, CUDA, OpenMP, PThread, Haskell. 1. Giriş Bilgisayar programcıları programlama sürecinin başından beri mevcut hesaplamayı daha da hızlı yapabilmek için büyük çaba harcamışlardır. Bu çaba özellikle modellemeler, mühendislik çalışmaları ve bilimsel hesaplamalarda daha zorunlu hale gelmiştir. Bu ihtiyaçlar ile paralel programlama kavramı ortaya çıkmıştır. Paralel programlama, bir problemin birden fazla bilgisayar veya işlemci kullanılmasıyla kısa sürede çözüme ulaşılması için kullanılan bir yöntemdir. Paralel programlamanın en temel kilit noktaları; haberleşme ve senkronizasyondur. Dolayısıyla bu kilit noktalarıyla birlikte donanımsal özellikler yazılımsal özellikler ile uyumlu olarak hareket etmelidir. Birden fazla işlem biriminin olması, birimlerin kontrolünde mimarilerine uygun yazılımlara yönelik işler yapılması gereğini ortaya koyar. Verimli bir kullanım ile mümkün olan optimal sürede işlem sonucunu ortaya koymak en temel amaç olmuştur. Böylece kullanımı kısıtlı olarak gerçekleştirilen paralel programlama değer görmeye başlamıştır. Ayrıca Paralel programlamaya uygun makinelerin de sayısı artmıştır [1]. Paralel programlama uygulandığı biçimler bakımından aşağıdaki farklılıkları içerir. Paralelliğin hangi kısımda yapıldığı Paralel program kısımlarının tanımlanma şekli İşlemcilerin haberleşmesi Paralel kısımların eşzamanlı çalışma prensipleri Uygulanan metotlar ile süper bilgisayarlar denilen normal bilgisayarlardan daha kısa sürede çok fazla veriyle problemin çözümünü yapan makineler ortaya çıkar [2]. 2. İş parçacığı Tabanlı Yöntemler 2.1 Java Thread ile Paralel Programlama Thread, bir programın birbirinden bağımsız işlem parçalarını yürütmekle görevli en küçük parçasıdır. Kullanıcı tarafından ve işlemci tarafından oluşturulan thread’ler olmak üzere iki farklı şekli vardır. Kullanıldığı yerlere bakılacak olursa, aynı görev içinde işlemcinin farklı thread’leri kullanıma koyması paralel programlamada karşılaşılan kısmıdır. Birçok programlama dilinde thread yapıları kütüphaneler yardımıyla oluşturulur. Bu kütüphanelerden kullanılışı açısından Java Thread uygulama ara yüzü en basit kullanıma sahip olanıdır. Her bir Java programı bir işi, main() metodu ile gerçekleştiren en azından bir thread ile iş yapar. Thread’ler arası senkronizasyon ve haberleşme işlerinin dengeli olabilmesi için mutex, lock, barriers ve deadlock gibi metotlara ihtiyaç duyulur [3]. 2.2 CUDA ile paralel programlama Cuda, Nvidia firması tarafından üretilen grafik işlemci birimi kullanılarak paralel programlama yapılan mimari çeşididir. CUDA işlemciyi kullanarak mimari yardımıyla kernel adı verilen programın serileştirilmesiyle paralel yapıya ulaşılır [4]. CUDA, SIMT (Single Instruction Multiple Thread) paralel programlama modeline sahiptir. Thread denilen yapılar uygun sayıda bir araya gelerek warp denilen yapıları, warp’lar birleşerek blok’ları, blok’lar birleşerek grid denen yapıları meydana getirirler. Paralel programlamanın işlemci mimarisiyle en optimal çalıştığı thread sayısı 32 dir. Bu sayı dışında olan durumlar için thread’lerde boşta bekleme (idle) denilen durum ortaya çıkar. Thread’ler kendi içlerinde yaptıkları işlerde birbirlerinden bağımsız hareket ederler. CPU ile GPU arasındaki haberleşme birbirinden bağımsız algoritma parçaları ile haberleşme trafiği az olacağından daha hızlı çalışır. Örneğin; birbirinin sonucunu bekleyen seri bir algoritma parçasından paralellik adına verimli sonuç almak pek mümkün değildir. Bu gibi durumlarda bant genişliği, haberleşme koşullarına bağlı olarak darboğaz (bottleneck) denilen durumların oluşması sıklıkla karşılaşılan bir durumdur. Bu gibi durumların çözümlenmesi için senkronizasyonun thread’ler arasında uygun komutlarla sağlanması gerekir [5]. 2.3 PThread ile Paralel Programlama Birçok çok çekirdekli işlemci thread’ler için bir POSIX standardı olarak bilinen POSIX THREAD (PThread) yapısını kullanır. PThread yapısı, C programlama dilinin tipleri, fonksiyonları ve sabitlerini içeren bir kütüphanedir. ‘pthread’ ön eki kullanılmak üzere yaklaşık 100 civarında PThread prosedürü bulunmaktadır. Bu prosedürler 4 ana başlık altında toplanabilir: Thread yönetimi (thread oluşturma, birleştirme,…) Mutex yapıları Koşul değişkenleri Senkronizasyon En çok kullanılan PThread metotları ise; pthread_create, pthread exit, pthread cancel, pthread attr init, pthread attr destroy şeklindedir [6]. 2.4 OpenMP ile Paralel Programlama OpenMP, shared memory mimarisini C, C++, Fortran dillerinde destekleyen bir ara yüzdür. Derleyici komutları, kütüphane metotları, ortam değişkenleri gibi yapıları bünyesinde barındırır. OpenMP kütüphanesi taşınabilir bir özellik sunması açısından bilgisayarcılar için paralelleştirmede önemli bir yöntemdir. Çoklu iş parçacığı yönetimini sağlar. Paralel çalıştırılacak kod kısımları thread yapılarına iş bölümü ile dağıtılır. Ompgetthreadnum() fonksiyonu ile her thread kendine özgü işi yapar. Her thread kendine verilen görevi bitirdikten sonra yeni bir iş parçası almak için tekrar kuyruğa girer. İşin tamamı bitene kadar bu döngü devam eder. Hem görev hem de veri paralelliği açısından OpenMP önemlidir [7]. 2.5 Haskell ile Paralel Programlama etmesini sağlayan önemli türevlerinden biridir [9]. 3. Dağıtık Yöntemler 3.1 MPI ile Paralel programlama MPI (Message Passing Interface), dağıtık mimarideki makinelerde paralel programlama yapmayı kolaylaştıran fonksiyonlar kütüphanesidir. Temel işleyişi, ayrık adres uzaylarına sahip işlemciler arasında mesaj alıp - verme yöntemi şeklindedir. İşlemciler arasında haberleşmeyi sağlamak için MPI_COMM_WORLD komutuyla bir ortam oluşturulur. Bununla birlikte, MPI_Init MPI_Finalize MPI_Comm_Size MPI_Comm_Rank MPI_Send MPI_Receive Haskell, GHC isimli derleyiciye sahip bir fonksiyonel programlama dilidir. Çok çekirdekli mimariler için geniş kütüphane seçeneğiyle oldukça etkilidir [8]. En sık kullanılan MPI komutlarıdır. Bir fonksiyonel programlama dili olarak Haskell, veri bağımsızlığı ile birbirinden bağımsız fonksiyonlardan oluştuğu için durum (state) değişikliği gözlenmediğinden paralellik açısından değerlidir. Kaynak ‘0’ olarak numaralandırılarak yani verinin yer aldığı ve dağıtılacağı bilgisayar numarası verilerek numaralandırma yapılır. Koşul ifadeleri yardımıyla da bileşen bilgisayarlara veri dağıtımı mesaj yoluyla gerçekleşir. Benzer yolla da verilerin toplanması esnasında mesajlar toplanarak işlenmiş veri elde edilir [10]. Haskell dili, paralelliği SMP (sharedmemory multi-processed) üzerinde gerçekleştirilir. Thread denilen bağımsız yapılarla paralel olarak işlemciler üzerinde istenilen işlemler kolaylıkla gerçekleştirilir. Parallel isimli ara yüz içinde par ve seq isimli iki fonksiyon ile işlem paralelleştirilir. Eden ve GpH, Haskell dilinin kullanıcıya görev paralelleştirmesini ve verileri ifade 4. Karakteristik Özellikler Java gibi nesnel bir dilde ise class yapısı içinde tip tanımlaması yapılabilir. Java dilinin önemli bir kısmını aşırı yüklenmiş metot yapısı oluşturur. Java dili genel olarak nesnelerle program altyapısını oluşturur. Hem çalışma sırasında hem de derleme aşamasında hata kontrolünü sürekli gerçekleştirir. En önemli özelliği platformdan bağımsız olmasıdır [11]. Haskell dili, dilin tasarımı gereği metot ile tip tanımını birbirinden ayırır. Tip sınıfı ile objenin kullanacağı kurallar belirlenerek bu tanımlama yapılır. Aşırı yüklenmiş metot yapısı bu dil tarafından desteklenmez. Public, Private gibi özellikler Haskell ’de yer almaz. Bunun yerine bir sınıfın bileşenleri modül sistemi içinde saklanıp, açıklanır. Bir fonksiyonel programlama dili olarak Haskell, polimorfik olması ve yanetkisiz çalışması ile diğer dillerden ayrılır. Atama deyiminin bulunmayışı beraberinde değerlerin çakışmamasını yani yan-etki durumunun ortadan kalkmasını sağlar. Polimorfizm ile de temel sınıflardan yola çıkılarak çeşitli obje yapılarının türetilmesini sağlar [12]. MPI, yapısı gereği hata kontrolünü yapması ve okunabilirliğinin kolay olması ile açıkça ifadesi mümkündür. Görev paralelleştirme metodunu kullanır. CUDA, mimariye uygun şekilde tekrar düzeltilebilir yapısı vardır. Class- metot’lar yardımıyla yeni fonksiyonlar üretebilir ve kolayca kullanılabilir. NVIDIA’nın tasarladığı Nsight modülü ile hata ve performans kontrolü açıkça listelenir. İşlemcilerin dikkatli bir şekilde etkin kullanımı gerçekleştirilir [13]. PThread, çok işlemcili bir bilgisayarda birçok thread yapısını aynı anda çalıştırır. Thread’ler birbirini engellemeyecek düzende iş yapar. Böylece, çok karmaşık işlemler de kolaylıkla thread’ler yardımıyla paralelleştirilir. OpenMp ise, taşınabilir ve ölçeklendirilebilir olması açısından paralelliğe uygundur. Bu karakteristik özellikler, matris çarpımı algoritması ile 1000x1000 tipli bir matriste performans incelemesini gösteren tablo aşağıdadır. Programla ma Dilleri OpenMP Donanım Süre Hızlan ma 3.320x Intel 3.802s Core 2 Quad Java Intel 8.73s 7x Thread Xeon Dual Core CUDA GeForce 0.25s 2.56x GTX 280 SC MPI Intel 0.111s 5.70x Core 2 Quad PThread 80.056s 0.9371 process x Sun Ent. 2 GB M. Haskell Intel 7.2s 3.8x Core i-7 6 GB M Tablo 1- Matris çarpımı algoritması ile Paralel programlama ortamlarının performansları Bu tabloda matris çarpımı algoritması, blok parçalama yöntemiyle oluşturulmuştur. Bu algoritmanın karmaşıklığı N, işlemci sayısı olmak üzere seri olarak O(N), paralel olarak O(log N) dır. Elde edilen performanslardan OpenMP platformu ile 4 kere çalıştırılıp ortalama süre hesaplanmıştır [14]. Java için, Java Hotspot JIT in Version 1.6.0_13 ortamında test edilmiştir [15]. CUDA platformu ise belirtilen GeForce işlemcisinde test edilmiştir [16]. MPI ile elde edilen sonuçlar matristeki blokların parçalanması yöntemiyle elde edilmiştir [17]. PThread için diğerlerinden farklı bir makinede yüksek bir performans elde edilmiştir [18]. Haskell platformunda GpH modülü yardımıyla elde edilen her thread yapısına bir blok karşılık gelecek şekilde sonuç elde edilmiştir [19]. A., "Paralel Hesaplama ve CUDA", 6th International Advanced Technologies Symposium 2011 (IATS'11), June 2011. [5] Akaydın, B., Tunçel M., “Introduction to CUDA”, ITU Cuda education, 2013. 5. Sonuç ve Öneriler MPI, Java Thread, CUDA, Haskell, PThread, OpenMP platformlarının paralellik ile gösterdiği karakteristiksel özellikler temel özellikleriyle birlikte incelenmiştir. MPI ve PThread platformları paralellik açısından daha gelişmiş bir ortama sahiptir. Matris çarpımında blok parçalama yöntemiyle incelenen platformlarda derlenerek performanslarındaki farklılıklar gözlenmektedir. Algoritmaya uygunluğu açısından etkin seçilen bir platform nanosaniye derecesinde de olsa daha hızlı sonuç verebilir. [6] Prasad J., “Shared Memory Programming with pthreads”, InterUniversity Centre for Astronomy & Astrophysics Pune, India 2012. [7] en.wikipedia.org/wiki/OpenMP [Son Erişim Tarihi: 18 Aralık 2014]. [8] https://www.haskell.org/haskellwiki/Hask ell_for_multicores [Son Erişim Tarihi: 10 Aralık 2014]. [9] Coutts, D., Löh, A., “Modern Programming languages”, Copublished by the IEEE CS and the AIP, 12, 1521-9615, 2012. 6. Kaynaklar [1] Erarslan, G., "Paralel Programlama ve MPI", http://seminer.linux.org.tr/wpcontent/uploads/ParalelProgramlamaveMP I.pdf [Son Erişim Tarihi:13 Aralık 2014]. [2] Ergün, U., Sayar, A., "Fonksiyonel Programlama Dilleri ile Paralel Programlama", Niğde Üniversitesi Mühendislik Bilimleri Dergisi, Cilt 3, Sayı 2, 1-17, 2014. [3] Meyer, B., Pedroni, M., "Concurrent Programming with Java Threads", Software Architecture, 2010, http://se.inf.ethz.ch/old/teaching/2010S/0050/slides/13_softarch_self_study_thre ads.pdf [Son Erişim Tarihi: 10 Aralık 2014]. [4] Akcay, M., Sen, B., Orak, I., M., Celik, [10] https://computing.llnl.gov/tutorials/mpi [Son Erişim Tarihi: 11 Aralık 2014]. [11] Çay T., İşcan F., “Harita Mühendisliğinde Kullanılan Programlama Dilleri ve Yazılımları”, Akademik Bilişim’02, 2002. [12] Sabel, D., Schmidt-Schauß, M., “Conservative Concurrency Haskell”, 27th Annual ACM/IEEE Symposium on Logic in Computer Science, 2012. [13] Bhardwaj D., “Parallel Computing”, Indian Institute of Technology, Delhi –110 016 India, 2002. [14] Chowdhury, R., “Parallel Computing with OpenMP to solve matrix Multiplication”, UCONN BIOGRID REU Summer, 2010. [15] Häuser, J. , “A Test Suite for HighPerformance Parallel Java”, Center for Advanced Computing Research, 2000. [16] Dotzler, G., “JCudaMP: OpenMP/Java on CUDA”, Programming Systems Group, 2010. [17] Quinn, M., “Parallel Programming in C with MPI and OpenMP”, International Edition, 2003. [18] http://www.cs.cmu.edu/~scandal/papers/sc 98/ [Son Erişim Tarihi: 18 Aralık 2014]. [19] Loidl, H-W., “Comparing Parallel Functional Languages : Programming and Performance”, Journal Higher-Order and Symbolic Computation, Kluwer Academic Publishers, Volume 16 Issue 3, 203-251, 2003.