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:

  1. o - początek przedziału całki
  2. 100 - koniec przedziału
  3. 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:

  1. Nazwa polecenia (u nas ruby)
  2. .o (tak - kropka i literka "o")
  3. ID zadania
  4. . (kropka)
  5. 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