Optymalizacja obliczeń zmiennoprzecinkowych

1

Hej, mam dwa pliki z których wczytuje ponad milion liczb zmiennoprzecinkowych, następnie wykonuje na nich obliczenia( parę pętli for itp). Niestety trwa to dość długo. Dane wczytuje do listy i potem wykonuje operacje na liście. Chciałem zapytać czy da się to jakoś przyśpieszyć? czy np numpy Array rzeczywiście szybciej by sobie radził ze wszystkim? Operacje na listach to głownie iteracja, dodawanie elementow, odejmowanie

0

Tak, numpy będzie wykonywał te operacje szybciej. Liczby w czystym Pythonie to obiekty, a operacje na obiektach wprowadzają pewien narzut, zarówno pamięciowy jak i obliczeniowy. Podobnie listy, w czystym Pythonie służą do przechowywania wielu rodzajów obiektów, niekoniecznie jednorodnych, przez co Python nie może łatwo optymalizować operacji na nich. numpy.Array jak i reprezentacja float w numpy są zoptymalizowane pod takie właśnie zastosowania.

0

Dzięki wielkie, to zabieram się za ogarnianie numpy

1

Nie korzystałem (jeszcze) z tych dwóch narzędzi, ale zamiast/obok numpy możesz też wypróbować:

  • Dask jeżeli wykonywane przez Ciebie operacje da się zrównoleglić

  • Numba do kompilacji w locie krytycznych fragmentów kodu Pythona

Myślę że dla dużych ilości danych i dużych ilości obliczeń korzyści powinny być większe niż narzut związany ze zrównoleglaniem / kompilacją. Według tego wpisu autorowi udało się uzyskać przyspieszenie ponad 60x dla jego przypadku, przy połączeniu sił obydwu.

0

podepnę się, bo to podobny problem chyba
Mam kod:
``
tol = 5
dane = [dane2.index(d2) for d1, d2 in zip(dane1, dane2) if -tol < d1 < tol]

``
Niestety kiedy dane sa listami zawierającymi okolo kilkuset lub ponad milion liczb to program trwa strasznie wolno. Zauważyłem ze operacja .index() tak zamula. Czy da sie jakos w inny szybszy sposob dostac indexy ?

0
Waz smutny napisał(a):

podepnę się, bo to podobny problem chyba
Mam kod:
``
tol = 5
dane = [dane2.index(d2) for d1, d2 in zip(dane1, dane2) if -tol < d1 < tol]

``
Niestety kiedy dane sa listami zawierającymi okolo kilkuset lub ponad milion liczb to program trwa strasznie wolno. Zauważyłem ze operacja .index() tak zamula. Czy da sie jakos w inny szybszy sposob dostac indexy ?

Pamiętaj, że index iteruje listę aż znajdzie taki sam item. Czyli masz O(n) operacji. Dodatkowo jeżeli masz taką sytuację:
d2 = [1,2,3,4,5,1]
to Twoja lista dla d2[-1] zwróci index 0.. bo index() wykryje 1 na początku i wyjdzie.

Aby uniknąć iterowania listy ponownie ( w celu szukania indexu) po prostu rób enumerate:

dane = [index for index, (dane1, dane2) in enumerate(zip(dane1, dane2)) if -tol < d1 < tol]

0

Hej,
jeżeli dobrze zrozumiałem, to może wystarczy odpowiednio posortować i zastosować binary search, powinno działać dużo szybciej.

0

Niestety nie mogę posortować danych , ponieważ są to próbki czasu i sygnału z czujnika. W takim razie podczas wykonywania programu nie pozostaje nic innego jak pójść na kawę :)

0

"""
A widziałeś mój komentarz wyżej? Swoją drogą dlaczego próbki czasu trzymasz osobno od sygnału z czujnika? (tak to rozumiem). To wygląda tak, jakbyś gdzieś wypełniał dane1 danymi z czujnika i gdzieś indziej dane2 timestampami (z tym samym okresem co pomiar czujnika). - AsterFV 4 minuty temu
"""

Tak, zastosowałem to z enumeratem ale w dalszym ciągu dla danych około 1mln trwa to około 7 minut. Właśnie tak to wygląda jak napisałeś, mam przetwornik analogowo-cyfrowy który mierzy z odpowiednim krokiem i w jednej tablicy zapisuje wartości sygnału a w drugiej odpowiadająca mu chwile czasu. Ja potem te dane sobie zipuje i przeszukuje i szukam kiedy sygnał przekracza pewien próg , a ze sygnał jest okresowy to co jakiś czas przekracza próg i ja chciałbym mieć wszystkie te przekroczenia odnotowane. np czasem jest ich 2500, a dla sygnału o mniejszej częstotliwości jest ich np 500 ( ja chciałbym notować chwile czasu kiedy nastąpiło przekroczenie)

0

Bardziej mi chodziło o to, że enumerate to jedyna opcja uzyskania poprawnych indexów (bo index() zwróci pierwszy z brzegu w liście).

Można poprawić architekturę. Nie ma sensu zapisywać timestampu osobno - timestamp ma tylko sens razem z wartością więc mamy przykład dla tuple:
[(value, timestamp), (value, timestamp)...]. Dzięki temu nie musisz nic zipować potem. I unikasz sytuacji, gdzie dane mogą ulec uszkodzeniu (np. jedna lista zostanie przesunięta... i wszystkie próbki z listy drugiej będą przypisane do złych timestampów).Teraz możesz podjąć decyzję:

  1. Zapisujesz wszystko (bo się przyda gdzieś) i potem filtrujesz. Zgodnie z sugestią @hurgadion możesz zapisywać sortując po wartości i póżniej łatwo wybrać przedział zgodnie z Twoim treshold (a nastepnie posortować po timestampie by dane miały sens przy analizie).
  2. Albo filtrujesz przy zapisie.

Jeżeli pomiar ma służyć tylko do zanotowania przekroczeń nie ma sensu notować chwil, dla których przekroczeń nie ma.

0
AsterFV napisał(a):

Bardziej mi chodziło o to, że enumerate to jedyna opcja uzyskania poprawnych indexów (bo index() zwróci pierwszy z brzegu w liście).

Można poprawić architekturę. Nie ma sensu zapisywać timestampu osobno - timestamp ma tylko sens razem z wartością więc mamy przykład dla tuple:
[(value, timestamp), (value, timestamp)...]. Dzięki temu nie musisz nic zipować potem. I unikasz sytuacji, gdzie dane mogą ulec uszkodzeniu (np. jedna lista zostanie przesunięta... i wszystkie próbki z listy drugiej będą przypisane do złych timestampów).Teraz możesz podjąć decyzję:

  1. Zapisujesz wszystko (bo się przyda gdzieś) i potem filtrujesz
  2. Albo filtrujesz przy zapisie.

Jeżeli pomiar ma służyć tylko do zanotowania przekroczeń nie ma sensu notować chwil, dla których przekroczeń nie ma.

Niestety nie mogę zapisywać od razu do krotki bo jest nagrywane kilka sygnałów i czas jest przechowywany osobno w słowniku i w tym słowniku tez osobno są tez sygnały i dla każdego jest ten sam czas . Notowany jest cały sygnał bo następnie jest przedstawiany na wykresie oraz te punkty w których nastąpiło przecięcie. Aktualnie ja już sobie poradziłem ze wszystkim , tylko szukam teraz optymalizacji czasowej.

0

To może inaczej. Jeżeli dane pakujesz do słownika, to przy pakowaniu od razu podziel na jakieś podprzedziały, będziesz miał mniej wyszukiwania... Zrób takie hashowanie (tylko trzeba dobrać odpowiednie parametry), wtedy łatwiej będzie znaleźć i chyba szybciej to co Ci potrzebne, np. dla danych:

a = [10, 12, 13, 30, 10, 23, 24, 12, 14, 7, 23, 24, 45, 34, 45, 78, 12]

mamy takie hashowanie (V jest słownikiem słowników):

V[0] = {7: [9]}
V[10] = {10: [0], 12: [1, 7, 16], 13: [2], 10: [4], 14: [8]}
V[20] = {23: [5, 10], 24: [6], 24: [11]}
V[30] = {30:[3], 34: [13]}
V[40] = {45: [12, 14]}
V[50] = {}
V[60] = {}
V[70] = {70: [15]}

Wtedy dla przedziału 5-15 mamy formułkę:

indexes = []
a = 10 * (5 // 10)
b = 10 * (15 // 10) + 10
for elem in V[a].keys():
    if V[a][elem]>=5:
       indexes += V[a][elem]
for elem in V[b].keys():
    if if V[b][elem]<=15:
        indexes += V[b][elem]

Taka segregacja danych powinna sporo przyspieszyć.

1

Dziwne, dla sygnału rzędu 1 miliona próbek czas obliczeń powinien być w sekundach, 5-20, w zależności od stopnia złożoności algorytmu wyznaczającego przejście przez zero. Dziwne jest też to zapisywanie czasu, kompletnie niepotrzebne przy próbkowaniu równomiernym. Algorytmy pomiarowe operują na czasie dyskretnym, czyli wyłącznie na indeksach. Czas oblicza się mnożąc numer indeksu przejścia przez zero przez okres próbkowania plus ewentualnie czas startu rejestracji sygnału. Dla przykładu program JavaScript operujący naiwnym algorytmem szukania miejsc przejścia przez zero, który skleciłem w 30 minut, potrzebuje prawie 10 razy więcej czasu na generowanie niż na same obliczenia trwające około 5-6 sekund dla 1000k próbek.

0

Problem rozwiązałem, dzięki wielkie za pomoc, działa już ładnie i szybko.

Zamiast :

time_idx = [time.index(tim) for sig, tim in zip(signal, time) if -tol < sig < tol]

napisałem:

time_idx= [x for x in range(len(signal)) if -tol < signal[x] < tol]

gdzie signal , tol to parametry funkcji.

1

Nowa wersja jest nie tylko szybsza - ona jest także przynajmniej poprawna. Jeśli signal = [2, 2, 3, 4, 5, 2], to stara wersja dla tol = 8 by zwróciła [0, 0, 2, 3, 4, 0], a nowa, zgodnie z oczekiwaniami [0, 1, 2, 3, 4, 5].

1 użytkowników online, w tym zalogowanych: 0, gości: 1