Самый эффективный алгоритм поиска максимума значений с двойной точностью

Каков наиболее эффективный способ найти максимальное значение в наборе переменных?

Я видел решения, такие как

private double findMax(double... vals) {
double max = Double.NEGATIVE_INFINITY;

for (double d : vals) {
   if (d > max) max = d;
}
    return max;
}

Но каков будет наиболее эффективный алгоритм для этого?


person MarkusWillson    schedule 07.12.2014    source источник
comment
хм, кажется, хороший вопрос. Поскольку я думаю, что вам всегда придется перебирать все значения в наборе, я бы уменьшил его, чтобы найти самый быстрый способ сделать if (d>max) max=d   -  person rupps    schedule 07.12.2014
comment
С точки зрения времени выполнения и нотации Big-O, вы должны коснуться/загрузить и сравнить каждый элемент хотя бы один раз. Ваш алгоритм делает именно это, поэтому, если сложность исходит от загрузки или сравнения, он кажется вполне оптимальным. Оставаясь независимым от языка, я бы сказал, что во многих случаях может помочь распараллеливание, т.е. на графическом процессоре эффективна операция параллельного сокращения. Я думаю, что все остальное будет зависеть от языка и/или оборудования (компилятора).   -  person Thomas    schedule 07.12.2014
comment
Держите набор отсортированным и возвращайте последний элемент.   -  person Ignacio Vazquez-Abrams    schedule 07.12.2014
comment
можно это параллелить? как бы вы справились с тем фактом, что в конце вам придется каким-то образом пересекать параллельные каналы, чтобы найти абсолютный максимум?   -  person rupps    schedule 07.12.2014
comment
@rupps : использование, например. OpenMP ›3.1 вы можете сделать это, просто добавив #pragma omp parallel for reduction(max : max) перед циклом for. Здесь второй максимум относится к double max. Он автоматически пересекает трубы.   -  person Thomas    schedule 07.12.2014
comment
Незначительного улучшения производительности можно добиться, установив переменную double max = vals[0];, т. е. в первое значение, и начать цикл со второго; он должен работать в любых практических реализациях (C, C# и т. д.).   -  person Alexander Bell    schedule 07.12.2014
comment
@rupps: это попадает в категорию удручающе параллельных, поскольку вообще не имеет значения, в каком порядке выполняются сравнения. Вы можете передать вторую половину списка другому потоку, затем, когда два потока найдут максимум в каждой половине, выберите между ними.   -  person Ben Voigt    schedule 07.12.2014
comment
@ Бен совершенно прав, сэр, на самом деле это одна из самых простых форм распараллеливания. Я запутался, думая, что сравниваю все элементы между собой, когда важны только конечные результаты.   -  person rupps    schedule 07.12.2014


Ответы (3)


Вы не можете уменьшить сложность ниже O(n), если список не отсортирован... но вы можете значительно улучшить постоянный фактор. Используйте СИМД. Например, в SSE вы должны использовать инструкцию MAXSS для выполнения 4 операций сравнения+выбора за один цикл. Немного разверните цикл, чтобы уменьшить стоимость логики управления циклом. А затем вне цикла найдите максимальное из четырех значений, захваченных в вашем регистре SSE.

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

person Ben Voigt    schedule 07.12.2014

Предполагая, что в списке нет элементов в каком-либо определенном порядке, алгоритм, который вы упомянули в своем вопросе, является оптимальным. Он должен просмотреть каждый элемент один раз, поэтому это занимает время, прямо пропорциональное размеру списка, O(n).

Не существует алгоритма нахождения максимума, который имеет более низкую верхнюю границу, чем O(n).

Доказательство: предположим, что существует алгоритм, который находит максимум списка менее чем за O(n) времени. Тогда должен быть хотя бы один элемент, который он не проверяет. Если алгоритм выбирает этот элемент как максимальный, злоумышленник может выбрать такое значение для элемента, которое меньше, чем один из проверяемых элементов. Если алгоритм выбирает какой-либо другой элемент в качестве максимального, злоумышленник может выбрать значение для элемента так, чтобы оно было больше, чем другие элементы. В любом случае алгоритм не сможет найти максимум.

person Peter Olson    schedule 07.12.2014

РЕДАКТИРОВАТЬ: это был мой ответ на попытку, но, пожалуйста, посмотрите комментарии, в которых @BenVoigt предлагает лучший способ оптимизировать выражение.


  • Вам нужно пройти весь список хотя бы один раз
  • поэтому нужно найти более эффективное выражение для if (d>max) max=d, если таковое имеется.

Предполагая, что нам нужен общий случай, где список не отсортирован (если мы оставим его отсортированным, мы просто выберем последний элемент, как указывает @IgnacioVazquez в комментариях), и немного изучим ветвь предсказание (Почему это быстрее обрабатывать отсортированный массив, чем несортированный массив?, см. 4-й ответ), выглядит так

 if (d>max) max=d;

можно более эффективно переписать как

 max=d>max?d:max; 

Причина в том, что первый оператор обычно преобразуется в ветвь (хотя это полностью зависит от компилятора и языка, но, по крайней мере, в C и C++, и даже в языке на основе VM, таком как Java происходит), а второй преобразуется в условный ход.

Современные процессоры имеют большой штраф в ответвлениях, если предсказание идет не так (конвейеры выполнения должны быть сброшены), в то время как условное перемещение — это атомарная операция, которая не влияет на конвейеры.

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

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

person rupps    schedule 07.12.2014
comment
...возможно, если вы отключили оптимизацию. И нет, обе ветви не берутся с равной вероятностью, если только ваши данные не получены из броуновского процесса. - person Ben Voigt; 07.12.2014
comment
мне интересно, почему бы и нет? если список полностью не отсортирован, как компилятор может угадать, какой способ выбрать? Разве это не тот случай, который точно описан в вопросе, который я связал? - person rupps; 07.12.2014
comment
К тому времени, когда вы дойдете до середины, существует 50%-ная вероятность того, что условие больше никогда не будет выполнено. Если у макса равные шансы попасть в каждую ячейку, то вероятность выбрать ветвь на итерации i равна 1 / (1 + i). То же, что и вероятность того, что максимум подсписка (0, i) находится в последнем слоте. Получается мало, довольно быстро (хотя и недостаточно быстро, чтобы сойтись). - person Ben Voigt; 07.12.2014
comment
хм, ты снова прав ;) ... вопрос, который я связал, по сравнению со значением посередине. В любом случае, как вы думаете, замена условного перехода ветвью приведет к (очень незначительной) оптимизации? (то, что компилятор может сделать автоматически на этапе оптимизации, согласитесь!) - person rupps; 07.12.2014
comment
Условные перемещения тоже запутаны, часто лучшая версия чего-то вроде этого greater = -(d > max); max = d&greater | max&~greater; Удобно, это очень хорошо работает с SIMD... в конце цикла ваш регистр SIMD содержит максимальные значения каждого столбца, а затем вам нужно начать пролистывать и объединение столбцов. Но, вероятно, лучшим подходом будет использование арифметики с насыщением: max += saturate(d - max); - person Ben Voigt; 07.12.2014
comment
вау ты мастер своего дела, приятно узнавать что-то новое! - person rupps; 07.12.2014
comment
Извините, я противоречу себе, сказав лучше всего о двух разных вещах. Первая — лучшая реализация общего тернарного оператора. Второй свойствен максимум (или минимум). - person Ben Voigt; 07.12.2014