Linux altında mpi ve parallel computing

Merhaba arkadaşlar,

Yazılım dünyasında çok fazla bilinmeyen bir teknoloji olan MPI ve Paralel düşünme ile ilgili giriş niteliğinde bir yazı hazırladım. Umarım ilgilenen arkadaşların işine yarar.

Parallel Computing

MPI (Message Passing Interface), paralel hesaplamalarda kullanılan metotlar için bir standart sağlamak amacıyla yaratılmış, paralel işleyen processlerin iletişimi için sunulmuş bir kütüphanedir.

Paralel programlamaya geçmeden önce, parallel ve sequential hesaplamaların nasıl çalıştığından bahsedelim;

paralel & sequential

Sequential hesaplamada, processler sırayla işlenir, biri sonlanmadan diğeri başlayamaz. Parallel hesaplamada ise varolan processler işlemcilere dağıtılarak, aynı anda birden fazla processin işlenmesine olanak sağlanır. Böylelikle processlerin birbirlerine bağımlılığı azaltılmış olur.

Şekilde görüldüğü gibi paralel hesaplamada, t zaman zarfında p1, p2, p3 ve p4 processleri, işlemcilere dağıtılarak aynı anda işleniyor ve tamamlanıyor. Aynı t zaman içinde sequential yöntemde yalnızca p1 tamamlanabiliyor ve tüm işin tamamlanması 4t kadar zaman alıyor.

Bu yazı dizisinde, bir problemin çözümü için yazılmış bir programın MPI ile nasıl paralelleştirileceğini ve paralel programlama yaklaşımını göreceğiz.

MPI ile Paralel Programlama:

Paralel Ortamın Hazırlanması :

MPI ile programlama yapabilmek için öncelikle MPI kütüphanesini ve paralel ortamı install etmemiz gerekiyor. Ben bu yazı dizisi için; paralel programlamada, Ubuntu işletim sistemi ve C dilini kullanacağım.

Öncelikle terminale gireceğimiz,

sudo apt-get install aptitude

sudo aptitude install build-essential

satırları ile C/C++ derlemeye hazır hale geliyoruz.

MPI kütüphanesi için terminalde şunu yazıyoruz :

sudo apt-get install openmpi-bin openmpi-dev

Kurulum tamamlandıktan sonra bir text editor ile MPI kodlarımızı yazıp, yine terminale yazacağımız aşağıdaki satır ile derleyebiliriz :

mpicc deneme.c –o denemempi

Burada “deneme.c”, yazdığımız kodları kaydettiğimiz, uzantısı illaki .c olmak zorunda olmayan dosyadır. Derleme tamamlandıktan sonra “denemempi” adlı çalıştırılabilir bir dosya ortaya çıkacak, bunuda yine terminale yazacağımız aşağıdaki satır ile çalıştırabiliriz :

mpirun –np islemcisayisi ./denemempi

Eğer kod doğru yazılmışsa, “islemcisayisi” kadar işlemci ile çalıştırılacak ve output verecektir.

Ubuntuda şimdilik herhangi bir derleyici ide kullanmayacağız. Fakat istendiği takdirde Eclipse için C/C++ ile paralel ortamda hazırlanılabilir.

Eclipse’in linux için Helios versionu indirip Help->Install New Softwares->Helios -http://download.eclipse.org/releases/helios ‘u seçiyoruz. Oradan altta gelecek olan yeni pencerede yüklenilebilecek updateler gösterilecek. Sırasıyla aşağıdakileri seçiyoruz.

–> PTP external Tools Framework TAU extension

–> PTP Graphical Explorer of MPI Programs(GEM)

–> PTP  Paralel Language Development Tools UPC Support

–> PTP  Paralel Performance Wizard(PPW)

–> PTP  Scable Communication Infrastructure(SCI)

–> Remote System Explorer End-User Runtime

–> Parallel Tools Platform(PTP) End-User Runtime

Bunları kurduktan sonra, artık eclipse ile MPI programlama yapabiliriz. Eclipse ile paralel programlamayı da bir başka yazı dizisinde anlatırız : )

MPI Programlama :

Artık basit bir MPI uygulaması yazabilmek için gereken herşeye sahibiz. Örneğimize geçmeden önce örnekte kullanılacak olan yapıları ve MPI programlama mantığını öğrenmekte fayda var.

MPI programlamada bilinmesi gereken basit bir mantık vardır. Yazılan kod belirtilen işlemcilerde aynı anda, fakat ayrı ayrı çalıştırılır. Bunun anlamı şudur :  siz bir MPI kodu yazdığınızda, bu kod her işlemcide ayrı ayrı çalıştırılacağı için, kodu öyle bir yazmalısınız ki o an hangi işlemcide çalıştırıldığını anlayabilsin ve ona göre davransın. Yani hangi kod bloğunun hangi işlemci tarafından çalıştırılacağını önceden belirtmeniz gerekir.

Paralel programlamada, o anki işlemciyi MPI_Comm_rank kodu ile belirleriz. Eğer işlemci numarası 0 olarak belirlenirse, bu “Master Processor” anlamına gelir. 0 harici tüm processorler birer slavedir.

Peki nedir bu Master ?

Master Processor, işlemi başlatan, organize eden işlemcidir. Örneğin elinizde 80 lik bir array ve 4 adet işlemci var. Siz bu array içinde bir sayı arayacaksını diyelim. Mantık şudur : eldeki 80 adet sayı, 4 işlemciye 20 şer olarak dağıtılır; her işlemci kendi 20 lik dilimi içerisinde istenen sayıyı arar, sonuçlar tek bir noktada toplanır. İşte o tek nokta master processordür. Master 20lik dilimi kendine alır, kalan dilimleride diğer işlemcilere dağıtır. Search işlemi bittikten sonra tüm sonuçları kendinde toplayıp gerekli outputu ekrana yazar. İşte tüm bu alma verme işlemlerini MPI’ın Send, Receive, Bcast, Scatter,Gather,Reduce gibi fonksyonları ile yapacağız.

MPI_Init( &argc, &argv ): Her programınızda olması gereken initialization yapan yapıdır.

MPI_Comm_rank( MPI_COMM_WORLD,&rank ) : Kodun çalıştırıldığı işlemcinin numarasını tutan “rank” değişkenine değer atar. Kodu çalıştıran işlemciyi tanımlamak için rank değişkenini kullanacağız.

MPI_Comm_size( MPI_COMM_WORLD,&size ) : Kaç işlemci ile çalışıldığını integer cinsinden “size” değişkenine atar.

MPI_Finalize() : MPI işlemlerini bitiren fonksyon.

Bunlar neredeyse tüm MPI programlarınızda bulunacak yapılardır. Biz bu örneğimizde, processler arası veri iletişimi için sadece MPI_Send ve MPI_Receive kullanacağız.

MPI_Send(
gönderilecek array,
belirtilen indexten itibaren kaç adım gidileceği,
gönderilen veri türü (MP_INT),
hangi işlemciye gönderileceği,
Tag değeri,
MPI_COMM_WORLD);

Bir işlemcinin elindeki veriyi, başkasına aktarmak için gereken yapıdır.

MPI_Recv(
Alınan verinin nereye konacağı,
Kaç değer alınacağı,
Alınan veri türü (MPI_INT),
Hangi işlemciden alınacağı,
Tag değeri,
MPI_COMM_WORLD,
Status değişkeni);

Başka bir işlemciden veri alma, alınan veriyi değişkene atamak için gereken yapı.

Blocking & Nonblocking Metodlar

MPI_Send ve MPI_Recv fonksyonları MPI ın “Blocking” metodlarıdır. Blocking metodlar send sonrası karşı tarafın gönderilen veriyi almalarını garanti ederler. Nonblocking metodlarda bu garanti yoktur.

Ard arda gönderilen mesaj1 ve mesaj2 yi, karşı taraf gönderilme sırasına göre önce 1 sonra 2 olarak alır. MPI bunu garanti eder.

Artık örneğimize geçebiliriz :

Bu örnekte, 100000 adet sayı arasında 7 sayısını arayacağız. Bunun için öncelikle sayıları işlemcilere dağıtacağız ve her işlemci kendi kısmı içerisinde 7 sayısını arayacak. İşlemciler kendi bulduğu sonuçları (local results) ekrana bastırdıktan sonra bu sonuçları toplam kaç adet 7 sayısı olduğunu hesaplamak amacıyla Master’a gönderecek, ve Master’da 100000 adet sayı içerisinde kaç adet 7 geçtiğini ekrana basacak.

Not: Örnek kodu adım adım yorum satırları ile açıklamaya çalıştım, bu sebeple aşağıdaki kodu yorum satırlarını ayırt edebilen bir editorde (NotePad++ gibi) incelemenizi tavsiye ederim.

//Öncelikle mpi ve C kütüphanelerini include ediyoruz 

#include
#include
#include

//Şimdi parçalar içerisindeki 7 sayısını sayacak bir fonksiyon yazalım.

int findNumber(int *arr, int sz, int needle){
int count = 0;
int i;
for (i=0;i<sz;i++)
if (arr[i] == needle)
count++;

return count;
}

//Artık main methoduna geçebiliriz.

void main (int argc, char * argv[]) //main için arg parametreleri önemli
{
const int ASIZE = 100000; // 100000 lik array için size belirliyoruz
int *a, *b;  // Kullanacağımız arrayler için pointerlar yaratacağız

int rank, size;
MPI_Init( &argc, &argv ); // initialization için gerekli kod
MPI_Comm_rank( MPI_COMM_WORLD,&rank ); // hangi processorde çalıştığımızı "rank" değişkenine atıyoruz

MPI_Comm_size( MPI_COMM_WORLD,&size ); // kaç işlemci olduğunu "size" değişkenine atıyoruz

int i,j, rc;
MPI_Status stat; // status'ü belirleyen kod. Bunu neredeyse hiç kullanmayacağız ama Receive fonksyonu bu veriyi istiyor.

// en önemli noktalardan birine geldik.
// burada 100000 lik arrayimizi işlemcilere nasıl paylaştıracağımızı belirleyeceğiz.
// şimdilik başlangıç olarak 100000 i bölebilen işlemcilerle çalışacağız.
// 2 işlemcimiz olduğunu varsayarsak, her işlemciye 50000 er sayı düşecektir

int msize = ASIZE / size;   //msize değeri işlemci başına düşen sayı miktarını belirleyecek.

// bir diğer önemli kısım hangi işlemcinin bu kodda hangi kısımı çalıştıracağı.
// bunu belirlemek için basit if yapıları kullanabiliriz
// eğer rank 0 ise, bu demektirki şu anda master processordeyiz ve master ı ilgilendiren işlemler yapılacak.
// dolayısıyla if(rank ==0) dan sonra, masterın görevi olan sayıları üretme ve dağıtma işlemlerini yapabiliriz

if (rank == 0){
srand(3122131231);
a = malloc(ASIZE*sizeof(int)); //arrayimize integer tipinde ASIZE kadar yer açıyoruz.

for (j=0;j<ASIZE;j++){  // arrayi random sayılarla dolduruyoruz
a[j]= rand() % 100;
}

// arrayi yarattıktan sonra yapmamız gereken şey bunu diğer işlemcilere dağıtmak
//bizim "size" kadar işlemcimiz var. Dolayısıyla bir for döngüsü ve MPI_Send ile bu işi halledebiliriz

for (j=1;j<size;j++){
//üretmiş olduğumuz a arrayinden j*msize kadar sonrasından başlayarak, msize kadarlık bir parçayı bir sonraki işlemciye gönderiyoruz.
rc = MPI_Send(&a[j*msize],msize,MPI_INT,j,3,MPI_COMM_WORLD);
//anlaşılır olabilmesi için trace edelim :
//ilk döngüde j 1 den başlayacak, msize da 2 işlemci ile başlatırsak 50000 oluyor. böylelikle bir sonraki işlemciye a[1*50000] ve bir sonraki 50000 sayı gönderilmiş oluyor
//gönderilmeyen ilk 50000 sayı ise, masterın işlemesi için bırakılıyor
}

int totCount = 0;

//master kendi kısmını hesaplanması için fonksyona yolluyor
totCount = findNumber(a,msize,7);
//kendi sonucunu ekrana basıyor.
printf("I am process # %d and I have found %d item = 7\n",rank,totCount);
int slaveCnt;

// hala if(rank==0) ın içindeyiz, hala master işlerini yapıyoruz.
// şu ana kadar masterın kendi hesaplamaları bitti, kendi kısmı içinde kaç adet 7 rakamı var buldu.
//şimdi diğer processlerden gelen verileri toplamamız gerekiyor.

for (j=1;j<size;j++){ //bunun için yine master hariç her işlemci için (işlemci 1 den başlayarak) receive methodu çalıştıracağız.

rc = MPI_Recv(&slaveCnt,1,MPI_INT,j,4,MPI_COMM_WORLD, &stat); // slaveler sadece tek bir sonuç döndüreceği için (bir array değil) 1 yazdık.
totCount += slaveCnt; // aldığımız sonuçlar slave işlemcilerin kendi payları içinde bulduğu 7 miktarı. Bu değeri toplam değere ekleyerek sonuç buluyoruz.
}
printf("Number of 7 in this array is %d",totCount); // master içinde son olarak yapmamız gereken şey, sonucu ekrana bastırmak oluyor.

}else{  // else demek rank ı 0 olmayan, yani master olmayan processorlerde yapılan işlerin belirlendiği yer demek.

b = malloc(msize*sizeof(int)); // masterın dağıttığı sayıları alabilmek için yer açıyoruz.
// her slavede "msize" kadar sayı olacağı için msize*integer kadar yer açtık.

rc = MPI_Recv(b, msize, MPI_INT, 0, 3, MPI_COMM_WORLD, &stat); // masterın dağıttığı "msize" kadar sayıyı b arrayine dolduruyoruz
// burada tag (3) değerinin neredeyse hiç önemi yok şimdilik. orası neden 3 diye düşünmenize gerek yok :)  32523 de diyebilirdiniz.

int cnt = findNumber(b,msize,7); // aldığımız arrayi, içinde kaç adet 7 geçtiğini saydırmak amacıyla fonksyona gönderiyoruz.
printf("I am process # %d and I have found %d item = 7\n",rank,cnt); // slave processor kendi sonucunu ekrana basıyor
rc = MPI_Send(&cnt,1,MPI_INT,0,4,MPI_COMM_WORLD); // ve yine kendi sonucunu, toplam sonuca ekletmek için master processe gönderiyor.
}

MPI_Finalize();
return 0;
}

Bu yazıda, paralel programlamanın temel taşları ile MPI programlamaya bir giriş yaptık. Bir sonraki yazılarda MPI_Send ve MPI_Recv kullanmadan, daha efficient ve zahmetsiz yollarla daha kompleks paralel programlar yazacağız.

Umarım faydalı olabilmişimdir.

İyi çalışmalar.

Bu yazı toplam 994 kere görüntülenmiştir.

0saves
Eğer yazıyı beğendiyseniz lütfen yorum bırakın veya diğer yazılardan haberdar olmak için RSS'e üye olun..

İlgili Yazılar:

  • İlgili yazı bulunamadı.

Yazar Hakkında


Yazar:

Hakkında / İlgi Alanları: MPI, C/C++, Java, C#/.NET | Senior Student of Atılım University, Department of Software Engineering
Kategori: C/C++, March 12th, 2011

Yazarlarımızdan , bu yazı dahil toplam 1 adet yazı yazmış.

2 yanıt

  • ahmet türker says:

    Bu aralar özellikle ihtiyacım olan şeydi.Gerçekten çok açıklayı ve derli toplu bir yazı olmuş.Elinize emeğinize sağlık.Çok teşekkürler.

  • kemal says:

    iyi hoş anlatmışsınız ama ben consoledan build compile edebiliyorken eclipse de edemiyorum dediklerinizin hepsini de harfiyen yerine getirdim. Bi de helios indirilmesi şart mıdır indigo versiyonu da var indigo da bu dediklerinizi yapabiliyor muyuz biraz daha açıklayıcı olursanız sevinirim paylaşım için teşekkürler.



Cevap yaz