Próbuję zoptymalizować symulację prostego systemu dynamicznego, w którym odpowiedź sieci, a także jej parametry (wagi) ewoluują zgodnie z prostymi równaniami liniowymi. Symulacja musi trwać dziesiątki milionów kroków, ale rozmiar sieci zwykle będzie mały. W związku z tym wydajność jest ograniczona w mniejszym stopniu przez produkty matrycowo-wektorowe, ale raczej przez tymczasowe tablice, powiązane kontrole i inne mniej widoczne czynniki. Ponieważ jestem nowy w Julia, doceniam wszelkie wskazówki, które pomogą zoptymalizować wydajność.Julia: optymalizacja symulacji prostego systemu dynamicznego
function train_network(A, T, Of, cs, dt)
N, I = size(T)
z = zeros(I)
r = zeros(N)
@inbounds for t in 1:size(cs, 1)
# precompute
Az = A*z
Ofr = Of*r
# compute training signal
@devec z += dt.*(Az + cs[t] - 0.5.*z)
I_teach = T*(Az + cs[t])
Tz = T*z
# rate updates
@devec r += dt.*(I_teach - Ofr - 0.1.*r)
# weight updates
for i in 1:I
@devec T[:, i] += dt.*1e-3.*(z[i].*r - T[:, i])
end
for n in 1:N
@devec Of[:, n] += dt.*1e-3.*(Tz.*r[n] - Of[:, n])
end
end
end
# init parameters
N, I = 20, 2
dt = 1e-3
# init weights
T = rand(N, I)*N
A = rand(I, I)
Of = rand(N, N)/N
# simulation time & input
sim_T = 2000
ts = 0:dt:sim_T
cs = randn(size(ts, 1), I)
Timing sieci (2.000.000 stopni) z
@time train_network(A, T, Of, cs, dt)
daje taktowania
3.420486 seconds (26.12 M allocations: 2.299 GB, 6.65% gc time)
Aktualizacja 1
Idąc za radą David Sanders dostałam pozbyć się makra devec i wypisać pętle. To istotnie zmniejsza alokacji tablicy i zwiększa wydajność o około 25%, tutaj są nowe numery:
2.648113 seconds (18.00 M allocations: 1.669 GB, 5.60% gc time)
Im mniejszy rozmiar sieci, tym większy impuls. Istotą zaktualizowanego kodu symulacji można znaleźć here.
Aktualizacja 2
wiele alokacji pamięci, są ze względu na produkty matrycy wektora. Tak, aby pozbyć się tych Wymieniłem te produkty przez działanie w miejscu Blas, BLAS.genv !, która zmniejsza czasy przez kolejne 25% i alokacji pamięci o 90%,
1.990031 seconds (2.00 M allocations: 152.589 MB, 0.69% gc time)
Zaktualizowany kod here .
Update 3
Największy stopień-1 Aktualizację można również zastąpić dwoma połączeniami w miejscu funkcji Blas, mianowicie BLAS.scal! do skalowania i BLAS.ger! dla aktualizacji klasy 1. Zastrzeżenie jest to, że oba połączenia są dość powolne, jeśli więcej niż jeden wątek jest używany (problem z OpenBLAS?), Więc najlepiej jest ustawić
blas_set_num_threads(1)
Jest przyrost taktowania do wielkości sieci 20 15% oraz zysk w wysokości 50% dla sieci o rozmiarze 50. nie ma więcej alokacje pamięci, a nowe czasy są
1.638287 seconds (11 allocations: 1.266 KB)
Znowu zaktualizowany kod można znaleźć here.
Update 4
pisałem podstawową Cython script porównanie wyników tej pory. Główną różnicą jest to, że nie używam żadnych wywołań do BLAS, ale mam pętle: Wstrzykiwanie niskopoziomowych wywołań BLAS jest bólem w Cythonie, a wywołania numpy dot mają zbyt dużo narzutów dla małych rozmiarów sieci (próbowałem ...).Parametry czasowe to
CPU times: user 3.46 s, sys: 6 ms, total: 3.47 s, Wall time: 3.47 s
, która jest mniej więcej taka sama jak wersja oryginalna (z której, jak dotychczas, goli zostało 50%).
Zastępowanie wycinków macierzy funkcją "sub" może dać małe zwiększenie, ponieważ dowolny plasterek przydzieli tymczasową tablicę. –
Drogi Colinie, dzięki za sugestię! Czy masz referencje dotyczące tego zachowania? Myślałem, że standardowe indeksowanie jest po prostu skróconą formą dla wycinka, a sub jest w istocie wycinane, ale z nieco innym zachowaniem dotyczącym wymiarów końcowych itp. W odniesieniu do kodu symulacji, wszystkie plasterki są teraz zamieniane na jawne pętle, więc nie jestem pewien jak używać "sub" w tym przypadku. – user45893
Sprawdź [tutaj] (http://julia.readthedocs.org/en/latest/manual/arrays/) i wyszukaj słowo "SubArray". Indeksowanie dowolnego * elementu * tablicy nie przydziela pamięci tymczasowej. Indeksowanie dowolnego * wycinka * tablicy tworzy tymczasową tablicę odpowiadającą wycinkowi. 'sub' omija to, tworząc' SubArray', który jest w istocie zbiorem indeksów używanych do odniesienia do oryginalnej tablicy. Operacje na 'SubArray' odwołują się do oryginalnej tablicy, zamiast tworzyć tymczasową tablicę w pamięci, stąd mój oryginalny komentarz. Jednak wszystkie są zbyteczne, jeśli nie masz już żadnych plasterków :-) –