2010-07-23 12 views
17

po wielu profilowaniu odkryłem, że ta metoda zajmuje większość% czasu obliczeń. Naprawdę nie widzę sposobu na optymalizację, ponieważ jest to okropna funkcja. (to jest ...) Może ktoś może mi pokazać jakiś fajny pomysł?Czy można zoptymalizować tę funkcję?

public static double perceivedLoudness(double L_G, double L_ETQ, double a0) { 
    double t1 = 1d + 1/4d * Math.pow(10d, 0.1d * (L_G - a0 - L_ETQ)); 
    double t2 = Math.pow(t1, 0.25); 
    return 0.064d * Math.pow(10, 0.025 * L_ETQ) * (t2 - 1); 
} 

Oto ulepszona wersja:

public static double perceivedLoudness(double L_G, double L_ETQ, double a0) { 
    double x = L_G - a0 - L_ETQ; 
    double t1 = 0.25 * Math.exp(0.230259 * x) + 1; 
    double t2 = Math.sqrt(Math.sqrt(t1)); 
    return ltqFactors[(int)L_ETQ] * (t2 - 1); 
} 

na wyszukanie dla ltqFactors idzie w ten sposób. ltqValues ​​posiada 20 punktów od podanej funkcji ltq, że wartość przybliżona powinna być wystarczająca.

for(int i = 0; i < etqValues.length; ++i) { 
    ltqFactors[(int)etqValues[i]] = 0.064d * Math.exp(etqValues[i] * 0.05756462732485114210d); 
    } 

Edycja: Po więcej Test przebiega z większą liczbą plików, przychodzę do prędkości ~ 100% up:

  • Stary: 6,2% z 7000000 wywołań
  • Nowość: 3, 2% 8000000 połączeń.

Dziękuję, do tej pory!

Edycja2: Nie wiem, którą odpowiedź zaakceptować. :( Z kilkoma innymi usprawnieniami (głównie tabele wyszukiwania) czas przetwarzania dla 9000 plików dźwiękowych spadł z 4: 30 min do 3: 28 min.

Pozostawię to pytanie otwarte, aby sprawdzić, czy są inne pomysły, ale potem przyjąć jedną odpowiedź

Edycja. Jestem trochę sfrustrowany teraz używam treeviewer jface aby pozwolić użytkownikowi przeglądanie wyników, a to potrzeba więcej czasu, aby zaktualizować niż sam obliczeń. /.

+0

Chcę wiedzieć, jak ktoś mógłby wymyślić taką funkcję. Te stałe wydają się takie przypadkowe! –

+1

@controlfreak http://ergo.ucsd.edu/~holcus/papers/JSNC2000.pdf – InsertNickHere

+0

Nie ma nic w tej metodzie, która powinna trwać długo, aby obliczyć, czy na pewno nie zajmuje ona większości%, ponieważ jest nazywane wiele razy? –

Odpowiedz

23

Twoja funkcja wydaje się być analityczna, sugerowałbym zastąpienie jej całkowicie metodą interpolacji. W ten sposób redukujesz kosztowne połączenia do Math.Pow do kilku operacji arytmetycznych.

Najlepsze w tym przypadku powinno być przybliżenie funkcji racjonalnej. Twoja funkcja może mieć bieguny w płaszczyźnie złożonej, zwykle pokonuje interpolację wielomianową.

Pamiętaj, że masz dwie zmienne: L_G - a0 - L_ETQ i L_ETQ. Interpolację należy wykonać tylko w jednej zmiennej.

Poszedłem na racjonalną aproksymację funkcji t2 w funkcji L_G - a0 - L_ETQ. Zapoznaj się z Receptami numerycznymi dotyczącymi technik implementacji.

Również w ostatniej części zastąpić

Math.pow(10, 0.025 * L_ETQ); 

przez

Math.exp(L_ETQ * 0.05756462732485114210d) 

(który exp(L_ETQ * 0.025 * log(10))).

Powinno ci być dobrze z garstką operacji arytmetycznych i jedną wykładniczą.

EDIT: See a graph of t2 as a function of L_G - a0 - L_ETQ.

EDIT: Wymień

double t1 = 1d + 1/4d * Math.pow(10d, 0.1d * (L_G - a0 - L_ETQ)); 
double t2 = Math.pow(t1, 0.25); 

przez

double x = L_G - a0 - L_ETQ; 
double t1 = 0.25 * Math.exp(0.230259 * x) + 1; 
double t2 = Math.sqrt(Math.sqrt(t1)); 

i powinieneś zdobyć więcej%. W tym momencie racjonalne przybliżenie może być nadmierne: masz dwa exp i dwa sqrt.

+0

@Alexandre Wydaje się to interesujące. Dzięki. – InsertNickHere

+0

Jedna uwaga - wiele operacji zmiennoprzecinkowych jest tak szybkich w nowoczesnych procesorach, że zamiast tego metoda interpolacji może czasami okazać się wolniejsza. – Steve314

+0

@steve: pow jest obliczane z expem i logiem i musi uwzględniać różne przypadki specjalne. Kontrola wzrokowa pokazuje idealnego kandydata do aproksymacji Padé w pobliżu "typowego" punktu, a nawet ~ 50 operacji arytmetycznych (co pozwala na bardzo dokładne przybliżenie) będzie szybsze niż pow. –

0
  1. spróbuj zapisać w pamięci podręcznej niektóre wartości (domyślam się, że L_G i L_ETQ nie są tą zmienną, prawda?)
  2. spróbuj wprowadzić kod zależny od architektury i użyj JNI.
3

Matematyka nie od razu wygląda na to, że można ją zmienić, aby uniknąć jakichkolwiek podwójnych obliczeń, więc sposób podejścia zależy od tego, jak ta funkcja jest używana i od tego, jak dokładne wyniki będą potrzebne.

Najlepszym rozwiązaniem byłoby uniknięcie ponownego obliczenia wartości dla tego samego zestawu wartości wejściowych. Czy twój kod może zapisać wyniki obliczeń dla tych samych wartości wejściowych? Jeśli nie, możesz mieć pamięć podręczną dla wartości, ale uważaj, że liczba podwójna może mieć bardzo wiele wartości, możesz złożyć duble w znanym przedziale (na przykład od 0 do 1 razy w liczby całkowite od 0 do 99).

3

Przypuszczam

double t2 = Math.sqrt(Math.sqrt(t1)); 

jest szybszy niż

double t2 = Math.pow(t1, 0.25); 
+0

Dodałem to. – InsertNickHere

+1

@Wkładaj: i czy uważasz, że jest on rzeczywiście szybszy? –

+1

@Joachim Nie jestem tak dobry w microbenchmarks, ale jeśli moje wyniki są poprawne, to jest znacznie szybsze. (~ Szybko: 33133194, Powolny: 617337165/nanosekundy). Zrobiłem 2147483 obliczeń z 100 "iteracjami" każdy do testowania. – InsertNickHere

2

W spoglądając na tego papieru odniesienia, wydaje się, że L_ETQ i A0 są po prostu funkcją częstotliwości (Bark) z dźwięk.

Przynajmniej można wymyślić tabelę wyników różnych obliczeń dla podanych częstotliwości. Na przykład buforuj wyniki:

.064 * Math.pow(10, 0.025 * L_ETQ) 

według częstotliwości. [Może również buforować (a0 + L_ETQ) * .1]

Również, prawdopodobnie niewielki efekt, jeśli w ogóle, ale chciałbym przekonwertować 1/4 do 0.25.

+0

Dodałem to. – InsertNickHere

1

Wstępnie wygeneruj tabelę odnośników dla zakresu wejść, z którymi program może sobie poradzić.

To nie jest szybsze! :)

1

buforowanie wyjścia, przed params wejściowych, może pomóc:

http://en.wikipedia.org/wiki/Memoization

+0

Byłbym bardziej zaniepokojony całkowitą ilością chybień w pamięci podręcznej. Jeśli ma bardzo szeroki zakres wartości, prawdopodobieństwo trafień w pamięci podręcznej będzie niewiarygodnie niskie. To jednak ograniczyłoby dalsze obliczanie tych samych zestawów wartości. Poza tym byłbym zainteresowany kosztem wyglądu dużego zestawu pamięci podręcznej [nawet z hashowaniem] – monksy

+0

L_G - a0 - L_ETQ może mieć ogromną zmienność, ponieważ poziom ciśnienia akustycznego mierzony jest podwójną precyzją i nie chcę stracić to. Z grubsza mówiąc, mogę mieć> 3600000 różnych wartości. (Przyjmując 1000 różnych ciśnień, wartości 20 a0 i 20 L_ETQ.) – InsertNickHere

1

nie został jeszcze wspomnieć tak będę.

Możesz rozważyć przejście z matematyki zmiennoprzecinkowej do liczby całkowitej. Operacje są trochę szybsze. Grafika zazwyczaj używa raczej matematyki całkowitej niż zmiennoprzecinkowej ze względu na sposób dodawania i przechowywania pływaków. Konwersja do i od, ale jestem pewien, że otrzymasz duży wzrost wydajności. Jedynym problemem z matematyką całkowitą jest to, że musisz określić, z jaką precyzją chcesz żyć.

+0

To może być poprawne, ale chcę/potrzebuję precyzji. – InsertNickHere

+2

Czy możesz ustalić ustalony górny limit liczby miejsc dziesiętnych? – monksy

0

Chciałbym take some stackshots against it, aby wyeliminować zgadywanie. W ten sposób mógłbym być pewien, że czas nie jest pobierany gdzie indziej, np. Podczas odczytywania danych i przekształcania ich w zmiennoprzecinkowe lub w otwieraniu/zamykaniu plików. Kiedy byłem pewien, że rutynowa procedura trwała przez znaczną część czasu, jestem prawie pewna, że ​​będzie spędzać prawie cały swój czas na połączeniach z funkcjami Matematyki oraz na konwersji L_ETQ na liczbę całkowitą. Może być możliwe zapamiętanie tych funkcji matematycznych. Z drugiej strony, jak powiedział Alexandre, możesz zrobić to wszystko z interpolacją.

Chodzi o to, że prawdopodobnie masz więcej niż jedną rzecz do optymalizacji. Jeśli zajmie to około 180 sekund, a jeśli 50%, powiedzmy, tego czasu ta procedura jest na stosie, to jeśli skrócisz jej czas o połowę, zmniejszysz czas o 45 sekund do 135 sekund. Jednak teraz ta procedura jest tylko na stosie przez 45/135 sekund lub 1/3. Oznacza to, że niektóre inne rzeczy wykorzystują pozostałe 2/3 lub 90 sekund, i założę się, że są pewne rzeczy, które możesz zoptymalizować. Jeśli możesz je skrócić do, powiedzmy, 45 sekund, wtedy łączna liczba spadnie do 90, a% wzięty przez procedurę matematyczną powraca do 50%, więc może możesz wycisnąć trochę z tego. Tak to idzie. Za każdym razem, gdy wycinasz jedną jego część, inne części zwiększają się w procentach, więc możesz iść za nimi, w kółko, aż naprawdę mocno je wycisnąłeś.

Powiązane problemy