obliczanie układu równań liniowych, mierzenie wydajności ruby

Na przykładzie obliczania układu równań liniowych, można sprawdzić wydajność skryptu Ruby wraz z zastosowaniem biblioteki GSL.

Obliczenia numeryczne

Reprezentacja układu równań liniowych w metodach numerycznych, odpowiada macierzy n x n, zawierającej w każdym wierszu jedno równanie. Do wyliczenia wartości każdej z n-zmiennych, posłużę się dekompozycją LU. Główne założenie metody, polega na przecięciu macierzy m wzdłuż diagonali i wydzieleniu dwóch osobnych macierzy – górnej i dolnej. Takie podejście, pozwoli na znaczne przyśpieszenie procesu wyliczenia wartości zmiennych 1.

Szczegółowe informacje dotyczące metody LU lub bardziej zaawansowanych, które są zawarte we wrapperze GSL do Ruby, można przeczytać tutaj 2

W pierwszym kroku, należy zadeklarować przestrzeń dla układu równań, oraz dla przykładu, zapełnić ją losowymi wartościami

n = 3
#deklaracja macierzy o wymiarach n x n
m = GSL::Matrix.alloc(n,n)

#zapelnienie losowymi danymi
n.times do |i|
 n.times do |j|
 m.set(i,j,rand)
 end
end

 puts "macierz:"
 m.print

po wykonaniu powyższego kodu, otrzymamy przykładową macierz:

macierz:
[ 1.953e-01 2.512e-01 1.112e-02
...
... ]

Następnie, należy zadeklarować wektor wyrazów wolnych b:

b = GSL::Vector::alloc(n)
n.times do |i|
 b.set(i, rand)
end

po wykonaniu, wektor będzie wyglądał tak jak na poniższym listingu

GSL::Vector
[ 1.523e-01 3.412e-02 ... ]

Mając gotowy zestaw zmiennych i przestrzeni, można przystąpić do właściwej operacji, czyli dekompozycji LU oraz obliczenia wartości zmiennych z układu równań.

lu, perm = m.LU_decomp
x = LU.solve(lu, perm, b)

puts "rozwiazanie:"
p x

puts "sprawdzenie:"
p m*x.col

Po poprawnym wykonaniu skryptu, otrzymamy wyniki w postaci wektora rozwiązań oraz wektora sprawdzającego wynik.

rozwiazanie:
GSL::Vector
[ 5.710e-01 3.841e-01 3.949e-01 ]

sprawdzenie
GSL::Vector::Col
[  8.056e-01
 3.803e-01
 5.894e-01 ]

Wydajność i czas wykonywania

Powyższa metoda rozwiązywania układu równań liniowych, może być ciekawym narzędziem do badania jak zmienia się wydajność i czas wykonywania obliczeń wraz ze wzrostem ilości danych. Poniższy program będzie zwiększał rozmiar macierzy m (za każdym obrotem o jeden) oraz zwracał czas wykonywania. Testowanie czasu będzie możliwe dzięki klasie Benchmark 3


#podłączanie wymaganych bibliotek i klas

require("gsl")
include GSL
include Linalg
require 'benchmark'

#rozmiar n x n macierzy
xx = 500

#stan początkowy licznika
n = 1
#uruchomienie pętli, która za każdym razem zwiększy rozmiar macierzy

xx.times do

puts" n = "+n.to_s
#deklaracja macierzy
m = GSL::Matrix.alloc(n,n)

#zapelnienie losowymi danymi
n.times do |i|
 n.times do |j|
 m.set(i,j,rand)
 end
end

puts "macierz:"
m.print

#deklaracja wektora
@@b = GSL::Vector::alloc(n)

n.times do |i|
 @@b.set(i, rand)
end

puts "wyrazy wolne:"
p @@b

Benchmark.bm(7) do |x8|
 x8.report("LU_decomp:") { @@lu, @@perm = m.LU_decomp }
 #x8.report("LU_solve: ") { @@x = LU.solve(@@lu, @@perm, @@b) }
end

puts "rozwiazanie:"
p @@x

p m*@@x.col
n=n+1
end

Zastosowanie zmiennych globalnych (oznaczonych przez symbol @@), pojawia się tutaj dlatego, ponieważ inaczej nie udało mi się przekazać wartości zmiennej z pętli mierzącej wydajność. Być może, istnieje bardziej profesjonalne rozwiązanie takiego problemu, jednak do tego przykładu, wybrane podejście spełnia dobrze swoje zadanie.
W wyniku uruchomienia skryptu, otrzymamy następujące wyniki


$ ruby LU.rb
 n = 1
 user  system      total        real
LU_decomp:  0.000000   0.000000   0.000000 (  0.000000)
LU_solve:   0.000000   0.000000   0.000000 (  0.000000)
 n = 2
 user     system      total        real
LU_decomp:  0.000000   0.000000   0.000000 (  0.000000)
LU_solve:   0.000000   0.000000   0.000000 (  0.000000)
 n = 3

....
LU_decomp:  2.534000   0.010000   2.544000 (  2.563000)
 n = 843

 user     system      total        real
LU_decomp:  2.534000   0.000000   2.534000 (  2.533000)
 n = 844
...

Na niskiej klasy sprzęcie testowym (CeleronM@1ghz, 2GB ram, winxp), wyraźne opóźnienia w obliczeniach pojawiają się już (lub dopiero) przy rozmiarach n = 800.

Na poniższym wykresie, widać zależność czasu dekompozycji macierzy do jej rozmiaru.

Inne przykłady badania wydajności, można znaleźć w ciekawym artykule, opublikowanym na skorks.com 4

Leave a Reply