Можно ли оптимизировать эту функцию? - PullRequest
17 голосов
/ 23 июля 2010

после профилирования я обнаружил, что этот метод занимает большую часть времени вычислений. Я не вижу способа оптимизировать, так как это ужасная функция. (это...) Может быть, кто-то может показать мне хорошую идею или около того?

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);
 }

Вот улучшенная версия:

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);
 }

Поиск ltqFactors идет по этому пути. Значения ltqValues ​​содержат 20 точек от данной функции ltq, что приблизительно должно быть достаточным.

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

Редактировать: после дополнительных тестовых прогонов с большим количеством файлов, я набираю скорость ~ 100%:

  • Старый: 6,2% при 7000000 звонков
  • Новое: 3,2% 8000000 звонков.

Спасибо тебе пока!

Edit2: я не знаю, какой ответ принять. :( С некоторыми другими улучшениями (в основном справочными таблицами) время обработки для 9000 звуковых файлов сократилось с 4: 30 минут до 3: 28 минут.

Я оставлю этот вопрос открытым, чтобы посмотреть, есть ли другие идеи, но затем приму один ответ.

Редактировать: Я сейчас немного расстроен. Я использую средство просмотра дерева JFace, чтобы позволить пользователю просматривать результаты, и для его обновления требуется больше времени, чем для самих вычислений. : /

Ответы [ 9 ]

23 голосов
/ 23 июля 2010

Ваша функция выглядит аналитической, я бы предложил полностью заменить ее методом интерполяции. Таким образом, вы сокращаете дорогие вызовы до Math.Pow до нескольких арифметических операций.

Лучшим в этом случае должна быть рациональная функция приближения. Ваша функция, вероятно, имеет полюсы в комплексной плоскости, это обычно побеждает полиномиальную интерполяцию.

Обратите внимание, что у вас есть две переменные: L_G - a0 - L_ETQ и L_ETQ. Интерполяция должна выполняться только в одной переменной.

Я бы пошел к рациональному приближению функции t2 как функции L_G - a0 - L_ETQ. Посмотрите на Численные Рецепты для методов реализации.

Также, для последней части, замените

Math.pow(10, 0.025 * L_ETQ); 

от

Math.exp(L_ETQ * 0.05756462732485114210d)

(что составляет exp(L_ETQ * 0.025 * log(10))).

Так что вы должны быть в порядке с горсткой арифметических операций и одной экспоненциальной.

EDIT: См. График t2 как функцию L_G - a0 - L_ETQ.

EDIT: Заменить

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

от

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

и вы должны получить еще немного%. На этом этапе рациональная аппроксимация может быть чрезмерной: у вас есть два опыта и два квадрата.

3 голосов
/ 23 июля 2010

Я бы догадался

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

быстрее

double t2 = Math.pow(t1, 0.25);
3 голосов
/ 23 июля 2010

Математика не сразу выглядит так, как будто ее можно переупорядочить, чтобы избежать повторяющихся вычислений, поэтому подход зависит от того, как используется эта функция и насколько вам нужны точные результаты.

Наилучшим подходом было бы избежать пересчета значения для того же набора входных значений.Может ли ваш код сохранить результаты вычислений для тех же значений ввода?Если нет, то у вас может быть кэш для значений, но будьте осторожны, так как значения типа double могут иметь очень много значений. Возможно, вы захотите сложить значения типа double в известный интервал (например, от 0 до 1, чтобы сложить целые числа от 0 до 99).

2 голосов
/ 23 июля 2010

Взглянув на эту статью, на которую вы ссылаетесь, кажется, что L_ETQ и a0 являются просто функцией частоты (Bark) звука.

Итак, по крайней мере, вы можете составить таблицу результатов различных расчетов для заданных частот. Например, кешировать результаты:

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

по частоте. [Может также кэшировать (a0 + L_ETQ) * .1]

Также, возможно, незначительный эффект, если таковой имеется, но я бы конвертировал 1/4 в 0,25.

1 голос
/ 23 июля 2010

Это еще не было упомянуто, поэтому я буду.

Возможно, вы захотите перейти от математики с плавающей запятой к целочисленной.Операции немного быстрее.Графика, как правило, использует целочисленную математику, а не плавающую из-за того, как плавающие элементы добавляются и хранятся.Вам придется конвертировать в и из, но я уверен, что вы получите значительное повышение производительности.Единственная проблема с целочисленной математикой заключается в том, что вы должны определить, с какой точностью вы готовы жить.

1 голос
/ 23 июля 2010

Кэширование выходов против входных параметров может помочь:

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

1 голос
/ 23 июля 2010

Предварительно сгенерируйте таблицу поиска для диапазона входных данных, которые может обработать ваша программа.

Это не становится быстрее, чем это! :)

0 голосов
/ 10 сентября 2010

Я бы сделал несколько стопок против него , чтобы исключить догадки.Таким образом, я мог быть уверен, что время не уходит в другое место, например, при чтении данных и преобразовании их в плавающую точку или при открытии / закрытии файлов.Когда я был уверен, что эта процедура занимает значительную часть времени, я почти уверен, что она будет тратить почти все свое время на вызовы математических функций и преобразование L_ETQ в целое число.Возможно, можно запомнить эти математические функции.Опять же, как сказал Александр, вы можете сделать все это с помощью интерполяции.

Дело в том, что у вас, вероятно, есть несколько вещей для оптимизации.Если это занимает около 180 секунд, и если, скажем, 50% этого времени находится в стеке, то если вы сократите его время вдвое, вы сократите время на 45 секунд до 135 секунд.Однако теперь эта процедура находится в стеке только в течение 45/135 секунд или 1/3.Это означает, что некоторые другие вещи используют другие 2/3 или 90 секунд, и я уверен, что есть некоторые вещи, которые вы также можете оптимизировать.Если вы можете сократить их, скажем, до 45 секунд, то общее количество уменьшится до 90, а процент, полученный в математической программе, вернется к 50%, так что, возможно, вы сможете выжать еще немного из этого.Вот как это происходит.Каждый раз, когда вы сокращаете одну его часть, другие части увеличиваются в процентах, так что вы можете идти за ними снова и снова, пока вы действительно не сожмете это как можно больше.

0 голосов
/ 23 июля 2010
  1. попытаться кэшировать некоторые значения (я полагаю, что L_G и L_ETQ - не та переменная, верно?)
  2. попытаться реализовать в коде, зависимом от архитектуры, и использовать JNI.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...