Table of Contents
Wstęp
Przyjęło się, że wykonywanie obliczeń z pomocą gridów jest domeną języków C, C++ i Javy. To samo tyczy się zarządzania gridem. Okazuje się jednak, że z powodzenie można do tego wykorzystać także Rubiego. Jako przykład wykorzystamy całkowanie metodą Monte Carlo, zaś zarządzać gridem będziemy za pośrednictwem DRMAA'y oraz biblioteki DRMAA4Ruby.
DRMAA4Ruby
DRMAA4Ruby jest to interfejs do języka Ruby - który jest bindingiem do DRMAA'y dla C. Dzięki niemu możemy w bardzo prosty sposób rozdzielać zadania na mniejsze fragmenty i kolejkować je na gridzie.
DRMAA4Ruby ma jednak także swoje wady - nowa wersja tej biblioteki nie jest kompatybilna z wciąż popularną wersją Rubiego - 1.8.7. Występują także problemy z odnalezieniem i użyciem modułu libdrmaa.so. Na szczęście - oba te problemy nie są trudne do rozwiązania.
DRMAA4Ruby znajdziemy tutaj. W katalogu lib/ znajdują się pliki:
- drmaa.rb
- original-drmaa.rb
Jeśli korzystasz z Rubiego w wersji 1.8.7 (lub starszej), zamiast ładować drmaa.rb musisz ładować original-drmaa.rb.
Drugim z problemów jest błąd podczas ładowania pliku libdrmaa.so. Ruby nie może go namierzyć. Aby to zmienić, wystarczy podać dokładną ścieżkę do tegoż pliku (można to zrobić używając polecenia find w powłoce). Podmiany ścieżki na bezwzględną dokonujemy w linijce 191 (dla original-drmaa.rb):
@libdrmaa = DL.dlopen('/sciezka/bezwzgledna/libdrmaa.so')
Po tym zabiegu biblioteka powinna ładować się (i działać bez problemu).
Monte Carlo
Nie będę tutaj opisywał jak dokładnie działa całkowanie metodą Monte Carlo - zainteresowanych odsyłam do wikipedii. Poniżej przedstawię jednak kod realizujący taką metodę całkowania (oczywiście w Rubym):
xp = ARGV[0].to_f xk = ARGV[1].to_f parts = ARGV[2].to_i part = ENV['SGE_TASK_ID'].to_i STEPS=10_000 step = (xk-xp)/parts p = xp+step*(part-1) k = p+step xp = p; xk = k f = lambda { |x| x*x+3 } dx=(xk-xp).to_f s = 0 STEPS.times { s += f.call(xp+rand*dx) } s = dx*s/STEPS print s
Kod nie jest skomplikowany, a dla nas ważne jest to jak go odpalamy. Przyjmuje on 3 parametry jawne:
- początek przedziału całkowania
- koniec przedziału całkowania
- ilość części na jakie będziemy dzielić nasze całe całkowanie
Co znaczy "ilość części"? Musimy pamiętać, że obliczenia wykonywane będą na gridzie, z tego względu musimy nasze zadanie rozbić na subzadania. Każde subzadanie będzie liczyć "kawałek" całki, po czym całość zsumujemy. Nasz kod ma jeszcze jeden parametr - brany ze zmiennej środowiskowej SGE_TASK_ID. Tym parametrem jest informacja który kawałek ma liczyć dana instancja programu. Po ustawieniu zmiennych nie pozostaje nam nic innego jak policzyć i wyświetlić wynik. To by było na tyle jeśli chodzi o samo zadanie wywoływane na gridzie. Nie ma tu wielkiej filozofii - wystarczy pamiętać o tym, że duże zadanie rozbijamy na mniejsze subtaski które są względem siebie niezależne (mogą funkcjonować autonomicznie).
DRMAA + Ruby + JobTemplate
Pora stworzyć kod który doda do grida nasze zadanie, a następnie zbierze wyniki i wyświetli. Na początek wymagane biblioteki i dwie stałe:
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'original-drmaa.rb' STD_PATH = "/home/path/" SOURCE_PATH = "/home/path/"
- STD_PATH - gdzie ma domyślnie zapisywać wyniki obliczeń z poszczególnych instancji zadania
- SOURCE_PATH - gdzie znajduje się nasze zadanie które chcemy uruchomić
Dalej zdefiniujemy sobie JobTemplate - który odwzorowuje nasze zadanie w gridzie:
class Carlos < DRMAA::JobTemplate def initialize super self.command = "/usr/bin/ruby" self.arg = ["#{SOURCE_PATH}/monte_carlo.rb", 0, 100, 10] self.stdout = ":#{STD_PATH}/" self.join = true end end
Warto zauważyć, że jako polecenie nie podajemy pliku z kodem ale ścieżkę do Rubiego. Plik z kodem stanowi pierwszy parametr (da się to obejść całkiem prosto - jak? Pozostawiam to wam).
Następnie podajemy 3 parametry:
- o - początek przedziału całki
- 100 - koniec przedziału
- 10 - ilość subzadań na gridzie
Uruchamiamy nasze zadanie (opis metod znajduje się na stronie DRMAA'y - można skorzystać z opisów do Javy, ponieważ interfejs jest praktycznie ten sam):
session = DRMAA::Session.new jobs = [] session.run_bulk(Carlos.new, 1, 10).each { |job| jobs << job }
Zanim przejdziemy do zbierania i sumowania wyników, dla przypomnienia wspomnę, że wyniki z subzadań zapisywane są przez DRMAA'ę - tak więc nie musimy się martwić o ich zapis, tak długo, jak długo drukujemy coś na ekranie. DRMAA numeruje pliki w mniej więcej taki sposób:
- Nazwa polecenia (u nas ruby)
- .o (tak - kropka i literka "o")
- ID zadania
- . (kropka)
- task ID
Np: ruby.o15.61
Wiedząc o tym i majac w tablicy jobs informacje o kolejnych taskach - możemy poiterować pliki i odczytać z nich wyniki. Następnie wystarczy suma i zwrócenie wyniku całościowego:
result = 0 session.wait_each{ |info| if ! info.wifexited? puts "failed: " + info.job else file_name = "#{STD_PATH}/ruby.o#{info.job}" begin File.open(file_name) do |inl| while (line = inl.gets) result += line.to_f end end File.delete(file_name) rescue end end } print result
I to by było na tyle - z pomocą tego kodu możemy policzyć na gridzie całkę metodą Monte Carlo. Całość kodu managera:
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'original-drmaa.rb' STD_PATH = "/home/path" SOURCE_PATH = "/home/path" class Carlos < DRMAA::JobTemplate def initialize super self.command = "/usr/bin/ruby" self.arg = ["#{SOURCE_PATH}/monte_carlo.rb", 0, 100, 10] self.stdout = ":#{STD_PATH}/" self.join = true end end session = DRMAA::Session.new jobs = [] session.run_bulk(Carlos.new, 1, 10).each { |job| jobs << job } result = 0 session.wait_each{ |info| if ! info.wifexited? puts "failed: " + info.job else file_name = "#{STD_PATH}/ruby.o#{info.job}" begin File.open(file_name) do |inl| while (line = inl.gets) result += line.to_f end end File.delete(file_name) rescue end end } print result
Leave a Reply