Mia艂a by膰 zagadka, ale zamiast tego zrobi艂 mi si臋 temat na Pche艂k臋, wi臋c dzi艣 poka偶臋 raz jeszcze zalety wektoryzacji.
Mamy do rozwi膮zania nast臋puj膮ce zadanie: bierzemy odcinek o d艂ugo艣ci 1 i dzielimy go na dwie cz臋艣ci w przypadkowym miejscu, z rozk艂adem r贸wnomiernym. D艂ugo艣ci obu odcink贸w mno偶ymy jedna przez drug膮. Nast臋pnie powtarzamy t臋 sam膮 operacj臋 dla ka偶dego z tych dw贸ch odcink贸w (a wi臋c dzielimy ka偶dy z nich na dwa odcinki w losowym miejscu i mno偶ymy - osobno dla ka偶dego z nich - d艂ugo艣ci w ka偶dej parze). Powsta艂e w ten spos贸b cztery odcinki poddajemy tej samej procedurze. Powsta艂e w ten spos贸b osiem odcink贸w... i tak dalej.
Po wykonaniu niesko艅czenie wielu takich podzia艂贸w i mno偶e艅, wszystkie otrzymane iloczyny sumujemy. Pytanie: ile wynosi ta suma?
Z zadaniem mo偶na si臋 pewnie zmierzy膰 algebraicznie (je偶eli s膮 tu jacy艣 odwa偶ni, zapraszam), ja zaatakowa艂em je symulacj膮 bo na podej艣ciu formalnym ugrz膮z艂em okrutnie.
Celem dzisiejszego wpisu jest pokazanie jak bardzo wektoryzacja mo偶e przyspieszy膰 tego typu symulacj臋. Zaczniemy od podej艣cia naiwnego:
import numpy as np import time czas_startu = time.time() def symuluj_podzia艂y(ile_poziom贸w): suma_razem = 0 bie偶膮ce_segmenty = [1.0] for _ in range(ile_poziom贸w): nast臋pne_segmenty = [] for segment in bie偶膮ce_segmenty: x = np.random.uniform(0, segment) iloczyn = x * (segment - x) suma_razem += iloczyn nast臋pne_segmenty.extend([x, segment - x]) bie偶膮ce_segmenty = nast臋pne_segmenty return suma_razem wynik = [symuluj_podzia艂y(20) for _ in range(10)] wynik_艣redni = np.mean(wynik) print(wynik_艣redni) czas_zako艅czenia = time.time() czas_wykonania = czas_zako艅czenia - czas_startu print(f"Czas wykonania: {czas_wykonania} sekund")
Na wyj艣ciu dosta艂em:
0.49985632477171577
Czas wykonania: 24.606609582901 sekund
Je偶eli przyjrzymy si臋 powy偶szemu kodowi, zobaczymy, 偶e wykonuje on 10 razy funkcj臋 symuluj_podzia艂y(20)
, kt贸ra z kolei buduje kolejne podzia艂y odcink贸w pocz膮wszy od pojedynczego odcinka o d艂ugo艣ci 1 a偶 do 220=1048576 (milion z niewielkim hakiem), czyli summa summarum musi "przerobi膰" oko艂o dw贸ch milion贸w odcink贸w.
A teraz ten sam pomys艂 zrealizowany w wersji z wektoryzacj膮:
import numpy as np import time czas_startu = time.time() def symuluj_podzia艂y(ile_poziom贸w): suma_razem = 0 bie偶膮ce_segmenty = np.array([1.0]) for poziom in range(ile_poziom贸w): liczba_segment贸w = 2 ** (poziom + 1) x = np.random.uniform(0, 1, liczba_segment贸w // 2) * bie偶膮ce_segmenty iloczyny = x * (bie偶膮ce_segmenty - x) suma_razem += np.sum(iloczyny) bie偶膮ce_segmenty = np.concatenate((x, bie偶膮ce_segmenty - x)) return suma_razem wyniki = [symuluj_podzia艂y(20) for _ in range(10)] wynik_艣redni = np.mean(wyniki) print(wynik_艣redni) czas_zako艅czenia = time.time() czas_wykonania = czas_zako艅czenia - czas_startu print(f"Czas wykonania: {czas_wykonania} sekund")
Wynik:
0.49983291120363094
Czas wykonania: 0.32900047302246094 sekund
Jak wida膰 identyczny algorytm dzi臋ki zastosowaniu wektoryzacji przyspieszy艂 mniej wi臋cej siedemdziesi臋ciopi臋ciokrotnie! Oznacza to r贸wnie偶, 偶e je偶eli dysponujemy bud偶etem czasowym z oryginalnej wersji skryptu, mo偶emy poprawi膰 dok艂adno艣膰 symulacji poprzez zwi臋kszenie liczby rekurencyjnych wywo艂a艅 z 20 do 26 (bo 26=64):
wyniki = [symuluj_podzia艂y(26) for _ in range(10)]
Wynik:
0.49998513006056455
Czas wykonania: 18.292614221572876 sekund
Mo偶naby pewnie jeszcze spr贸bowa膰 z PyPy, ale ju偶 mi si臋 nie chcia艂o.
Co艣 oszukujesz. Tu wydruki z mojego programu (czas w sekundach):
—-
liczba poziom贸w: 20
suma = 0,499853456900863 (1048575 segment贸w)
czas = 00:00:00
liczba poziom贸w: 21
suma = 0,499872360965172 (2097151 segment贸w)
czas = 00:00:00
liczba poziom贸w: 22
suma = 0,499897206843164 (4194303 segment贸w)
czas = 00:00:00
liczba poziom贸w: 23
suma = 0,499918290331034 (8388607 segment贸w)
czas = 00:00:00
liczba poziom贸w: 24
suma = 0,49997957064264 (16777215 segment贸w)
czas = 00:00:01
liczba poziom贸w: 25
suma = 0,499987464149637 (33554431 segment贸w)
czas = 00:00:02
liczba poziom贸w: 26
suma = 0,499994476817779 (67108863 segment贸w)
czas = 00:00:05
liczba poziom贸w: 27
suma = 0,499994351481675 (134217727 segment贸w)
czas = 00:00:11
—-
A tu program:
Wszystko pi臋knie, tylko twoja procedura wykonuje obliczenia raz. A moja powtarza symulacj臋 dziesi臋ciokrotnie i zwraca 艣redni膮:
Pojedyncze wykonanie z wektoryzacj膮 wykonuje si臋 u mnie w 0.032 sekund, a bez niej w 2.61 sekund (dla 20 poziom贸w rekurencji).