У меня есть алгоритм, написанный на Java, который я хотел бы сделать более эффективным. Часть, которую, я думаю, можно сделать более эффективной, - это найти наименьшее из 3 чисел. В настоящее время я использую метод Math.min
как Math.min
ниже:
double smallest = Math.min(a, Math.min(b, c));
Насколько это эффективно? Будет ли эффективнее заменить на операторы if, как показано ниже:
double smallest;
if (a <= b && a <= c) {
smallest = a;
} else if (b <= c && b <= a) {
smallest = b;
} else {
smallest = c;
}
Или если какой-либо другой способ более эффективен
Мне интересно, стоит ли менять то, что я сейчас использую?
Любое увеличение скорости было бы очень полезно
Нет, это серьезно не стоит меняться. Такие улучшения, которые вы получите, когда играете с микро-оптимизациями, как это, не будут стоить того. Даже стоимость вызова метода будет удалена, если функция min
называется достаточно.
Если у вас возникла проблема с вашим алгоритмом, лучше всего посмотреть на макрооптимизации (например, выбор или настройка алгоритма) - вы, как правило, получите гораздо лучшие улучшения производительности.
И ваш комментарий, что удаление Math.pow
дает улучшения, может быть правильным, но это потому, что это относительно дорогостоящая операция. Math.min
не будет даже близко к цене с точки зрения стоимости.
Для многих методов типа утилит библиотеки apache-сообществ имеют простую реализацию, с помощью которой вы можете либо использовать, либо получить дополнительную информацию. В этом случае существует метод поиска наименьшего из трех двухлокальных номеров, доступных в org.apache.commons.lang.math.NumberUtils. Их реализация на самом деле почти идентична вашей первоначальной мысли:
public static double min(double a, double b, double c) {
return Math.min(Math.min(a, b), c);
}
double smallest = a;
if (smallest > b) smallest = b;
if (smallest > c) smallest = c;
Не обязательно быстрее, чем ваш код.
Позвольте мне вначале повторить то, что уже сказали другие, цитируя статью "Структурированное программирование с переходом к заявлениям" Дональда Кнута:
Мы должны забыть о небольшой эффективности, скажем, около 97% времени: преждевременная оптимизация - корень всего зла.
Однако мы не должны упускать наши возможности в этих критических 3%. Хороший программист не уклонится от самоуспокоения такими рассуждениями, он будет мудрым взглянуть на критический код; но только после того, как этот код был идентифицирован.
(выделение мной)
Итак, если вы определили, что кажущаяся тривиальная операция, такая как вычисление минимума трех чисел , является фактическим узким местом (то есть "критическим 3%" ) в вашем приложении, тогда вы может рассмотреть возможность его оптимизации.
И в этом случае это действительно возможно: метод Math#min(double,double)
в Java имеет очень специальную семантику:
Возвращает меньшее из двух двойных значений. То есть результат - это значение, которое ближе к отрицательной бесконечности. Если аргументы имеют одинаковое значение, результат будет таким же значением. Если любое значение NaN, то результатом является NaN. В отличие от операторов численного сравнения этот метод считает отрицательный ноль строго меньше положительного нуля. Если один аргумент положительный ноль, а другой отрицательный ноль, результат отрицательный 0.
Можно взглянуть на реализацию и увидеть, что она на самом деле довольно сложная:
public static double min(double a, double b) {
if (a != a)
return a; // a is NaN
if ((a == 0.0d) &&
(b == 0.0d) &&
(Double.doubleToRawLongBits(b) == negativeZeroDoubleBits)) {
// Raw conversion ok since NaN can't map to -0.0.
return b;
}
return (a <= b) ? a : b;
}
Теперь, возможно, важно указать, что это поведение отличается от простого сравнения. Это можно легко рассмотреть в следующем примере:
public class MinExample
{
public static void main(String[] args)
{
test(0.0, 1.0);
test(1.0, 0.0);
test(-0.0, 0.0);
test(Double.NaN, 1.0);
test(1.0, Double.NaN);
}
private static void test(double a, double b)
{
double minA = Math.min(a, b);
double minB = a < b ? a : b;
System.out.println("a: "+a);
System.out.println("b: "+b);
System.out.println("minA "+minA);
System.out.println("minB "+minB);
if (Double.doubleToRawLongBits(minA) !=
Double.doubleToRawLongBits(minB))
{
System.out.println(" -> Different results!");
}
System.out.println();
}
}
Однако: если обработка NaN
и положительного/отрицательного нуля не имеет отношения к вашему приложению, вы можете заменить решение, основанное на Math.min
, на решение, основанное на простом сравнении, и посмотреть, это имеет значение.
Это, конечно, будет зависящим от приложения. Вот простой, искусственный микробенчмарк (, который нужно взять с зерном соли!)
import java.util.Random;
public class MinPerformance
{
public static void main(String[] args)
{
bench();
}
private static void bench()
{
int runs = 1000;
for (int size=10000; size<=100000; size+=10000)
{
Random random = new Random(0);
double data[] = new double[size];
for (int i=0; i<size; i++)
{
data[i] = random.nextDouble();
}
benchA(data, runs);
benchB(data, runs);
}
}
private static void benchA(double data[], int runs)
{
long before = System.nanoTime();
double sum = 0;
for (int r=0; r<runs; r++)
{
for (int i=0; i<data.length-3; i++)
{
sum += minA(data[i], data[i+1], data[i+2]);
}
}
long after = System.nanoTime();
System.out.println("A: length "+data.length+", time "+(after-before)/1e6+", result "+sum);
}
private static void benchB(double data[], int runs)
{
long before = System.nanoTime();
double sum = 0;
for (int r=0; r<runs; r++)
{
for (int i=0; i<data.length-3; i++)
{
sum += minB(data[i], data[i+1], data[i+2]);
}
}
long after = System.nanoTime();
System.out.println("B: length "+data.length+", time "+(after-before)/1e6+", result "+sum);
}
private static double minA(double a, double b, double c)
{
return Math.min(a, Math.min(b, c));
}
private static double minB(double a, double b, double c)
{
if (a < b)
{
if (a < c)
{
return a;
}
return c;
}
if (b < c)
{
return b;
}
return c;
}
}
(Отказ от ответственности: Microbenchmarking в Java - это искусство, и для получения более надежных результатов следует рассмотреть возможность использования JMH или Caliper).
Запуск этого с помощью JRE 1.8.0_31 может привести к чему-то вроде
....
A: length 90000, time 545.929078, result 2.247805342620906E7
B: length 90000, time 441.999193, result 2.247805342620906E7
A: length 100000, time 608.046928, result 2.5032781001456387E7
B: length 100000, time 493.747898, result 2.5032781001456387E7
Это, по крайней мере, предполагает, что здесь можно было бы выжать несколько процентов (опять же, в очень искусственном примере).
Анализируя это далее, просмотрев вывод разборки горячих точек, созданный с помощью
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MinPerformance
можно увидеть оптимизированные версии обоих методов, minA
и minB
.
Во-первых, вывод для метода, который использует Math.min
:
Decoding compiled method 0x0000000002992310:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000000001c010910} 'minA' '(DDD)D' in 'MinPerformance'
# parm0: xmm0:xmm0 = double
# parm1: xmm1:xmm1 = double
# parm2: xmm2:xmm2 = double
# [sp+0x60] (sp of caller)
0x0000000002992480: mov %eax,-0x6000(%rsp)
0x0000000002992487: push %rbp
0x0000000002992488: sub $0x50,%rsp
0x000000000299248c: movabs $0x1c010cd0,%rsi
0x0000000002992496: mov 0x8(%rsi),%edi
0x0000000002992499: add $0x8,%edi
0x000000000299249c: mov %edi,0x8(%rsi)
0x000000000299249f: movabs $0x1c010908,%rsi ; {metadata({method} {0x000000001c010910} 'minA' '(DDD)D' in 'MinPerformance')}
0x00000000029924a9: and $0x3ff8,%edi
0x00000000029924af: cmp $0x0,%edi
0x00000000029924b2: je 0x00000000029924e8 ;*dload_0
; - MinPerformance::minA@0 (line 58)
0x00000000029924b8: vmovsd %xmm0,0x38(%rsp)
0x00000000029924be: vmovapd %xmm1,%xmm0
0x00000000029924c2: vmovapd %xmm2,%xmm1 ;*invokestatic min
; - MinPerformance::minA@4 (line 58)
0x00000000029924c6: nop
0x00000000029924c7: callq 0x00000000028c6360 ; OopMap{off=76}
;*invokestatic min
; - MinPerformance::minA@4 (line 58)
; {static_call}
0x00000000029924cc: vmovapd %xmm0,%xmm1 ;*invokestatic min
; - MinPerformance::minA@4 (line 58)
0x00000000029924d0: vmovsd 0x38(%rsp),%xmm0 ;*invokestatic min
; - MinPerformance::minA@7 (line 58)
0x00000000029924d6: nop
0x00000000029924d7: callq 0x00000000028c6360 ; OopMap{off=92}
;*invokestatic min
; - MinPerformance::minA@7 (line 58)
; {static_call}
0x00000000029924dc: add $0x50,%rsp
0x00000000029924e0: pop %rbp
0x00000000029924e1: test %eax,-0x27623e7(%rip) # 0x0000000000230100
; {poll_return}
0x00000000029924e7: retq
0x00000000029924e8: mov %rsi,0x8(%rsp)
0x00000000029924ed: movq $0xffffffffffffffff,(%rsp)
0x00000000029924f5: callq 0x000000000297e260 ; OopMap{off=122}
;*synchronization entry
; - MinPerformance::minA@-1 (line 58)
; {runtime_call}
0x00000000029924fa: jmp 0x00000000029924b8
0x00000000029924fc: nop
0x00000000029924fd: nop
0x00000000029924fe: mov 0x298(%r15),%rax
0x0000000002992505: movabs $0x0,%r10
0x000000000299250f: mov %r10,0x298(%r15)
0x0000000002992516: movabs $0x0,%r10
0x0000000002992520: mov %r10,0x2a0(%r15)
0x0000000002992527: add $0x50,%rsp
0x000000000299252b: pop %rbp
0x000000000299252c: jmpq 0x00000000028ec620 ; {runtime_call}
0x0000000002992531: hlt
0x0000000002992532: hlt
0x0000000002992533: hlt
0x0000000002992534: hlt
0x0000000002992535: hlt
0x0000000002992536: hlt
0x0000000002992537: hlt
0x0000000002992538: hlt
0x0000000002992539: hlt
0x000000000299253a: hlt
0x000000000299253b: hlt
0x000000000299253c: hlt
0x000000000299253d: hlt
0x000000000299253e: hlt
0x000000000299253f: hlt
[Stub Code]
0x0000000002992540: nop ; {no_reloc}
0x0000000002992541: nop
0x0000000002992542: nop
0x0000000002992543: nop
0x0000000002992544: nop
0x0000000002992545: movabs $0x0,%rbx ; {static_stub}
0x000000000299254f: jmpq 0x000000000299254f ; {runtime_call}
0x0000000002992554: nop
0x0000000002992555: movabs $0x0,%rbx ; {static_stub}
0x000000000299255f: jmpq 0x000000000299255f ; {runtime_call}
[Exception Handler]
0x0000000002992564: callq 0x000000000297b9e0 ; {runtime_call}
0x0000000002992569: mov %rsp,-0x28(%rsp)
0x000000000299256e: sub $0x80,%rsp
0x0000000002992575: mov %rax,0x78(%rsp)
0x000000000299257a: mov %rcx,0x70(%rsp)
0x000000000299257f: mov %rdx,0x68(%rsp)
0x0000000002992584: mov %rbx,0x60(%rsp)
0x0000000002992589: mov %rbp,0x50(%rsp)
0x000000000299258e: mov %rsi,0x48(%rsp)
0x0000000002992593: mov %rdi,0x40(%rsp)
0x0000000002992598: mov %r8,0x38(%rsp)
0x000000000299259d: mov %r9,0x30(%rsp)
0x00000000029925a2: mov %r10,0x28(%rsp)
0x00000000029925a7: mov %r11,0x20(%rsp)
0x00000000029925ac: mov %r12,0x18(%rsp)
0x00000000029925b1: mov %r13,0x10(%rsp)
0x00000000029925b6: mov %r14,0x8(%rsp)
0x00000000029925bb: mov %r15,(%rsp)
0x00000000029925bf: movabs $0x515db148,%rcx ; {external_word}
0x00000000029925c9: movabs $0x2992569,%rdx ; {internal_word}
0x00000000029925d3: mov %rsp,%r8
0x00000000029925d6: and $0xfffffffffffffff0,%rsp
0x00000000029925da: callq 0x00000000512a9020 ; {runtime_call}
0x00000000029925df: hlt
[Deopt Handler Code]
0x00000000029925e0: movabs $0x29925e0,%r10 ; {section_word}
0x00000000029925ea: push %r10
0x00000000029925ec: jmpq 0x00000000028c7340 ; {runtime_call}
0x00000000029925f1: hlt
0x00000000029925f2: hlt
0x00000000029925f3: hlt
0x00000000029925f4: hlt
0x00000000029925f5: hlt
0x00000000029925f6: hlt
0x00000000029925f7: hlt
Можно видеть, что обработка особых случаев требует определенных усилий - по сравнению с выходом, который использует простые сравнения, что довольно просто:
Decoding compiled method 0x0000000002998790:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000000001c0109c0} 'minB' '(DDD)D' in 'MinPerformance'
# parm0: xmm0:xmm0 = double
# parm1: xmm1:xmm1 = double
# parm2: xmm2:xmm2 = double
# [sp+0x20] (sp of caller)
0x00000000029988c0: sub $0x18,%rsp
0x00000000029988c7: mov %rbp,0x10(%rsp) ;*synchronization entry
; - MinPerformance::minB@-1 (line 63)
0x00000000029988cc: vucomisd %xmm0,%xmm1
0x00000000029988d0: ja 0x00000000029988ee ;*ifge
; - MinPerformance::minB@3 (line 63)
0x00000000029988d2: vucomisd %xmm1,%xmm2
0x00000000029988d6: ja 0x00000000029988de ;*ifge
; - MinPerformance::minB@22 (line 71)
0x00000000029988d8: vmovapd %xmm2,%xmm0
0x00000000029988dc: jmp 0x00000000029988e2
0x00000000029988de: vmovapd %xmm1,%xmm0 ;*synchronization entry
; - MinPerformance::minB@-1 (line 63)
0x00000000029988e2: add $0x10,%rsp
0x00000000029988e6: pop %rbp
0x00000000029988e7: test %eax,-0x27688ed(%rip) # 0x0000000000230000
; {poll_return}
0x00000000029988ed: retq
0x00000000029988ee: vucomisd %xmm0,%xmm2
0x00000000029988f2: ja 0x00000000029988e2 ;*ifge
; - MinPerformance::minB@10 (line 65)
0x00000000029988f4: vmovapd %xmm2,%xmm0
0x00000000029988f8: jmp 0x00000000029988e2
0x00000000029988fa: hlt
0x00000000029988fb: hlt
0x00000000029988fc: hlt
0x00000000029988fd: hlt
0x00000000029988fe: hlt
0x00000000029988ff: hlt
[Exception Handler]
[Stub Code]
0x0000000002998900: jmpq 0x00000000028ec920 ; {no_reloc}
[Deopt Handler Code]
0x0000000002998905: callq 0x000000000299890a
0x000000000299890a: subq $0x5,(%rsp)
0x000000000299890f: jmpq 0x00000000028c7340 ; {runtime_call}
0x0000000002998914: hlt
0x0000000002998915: hlt
0x0000000002998916: hlt
0x0000000002998917: hlt
Можно ли сказать, есть ли случаи, когда такая оптимизация действительно влияет на приложение, трудно. Но, по крайней мере, нижняя строка:
Math#min(double,double)
не аналогичен простому сравнению, а обработка особых случаев не предоставляется бесплатноMath#min
, не нужна, и тогда более эффективный подход может быть более эффективнымmin(double,double,double)
для лучшего удобства и удобочитаемости, а затем было бы легко выполнить два прогона с различными реализациями и посмотреть, действительно ли это влияет на производительность.(Замечание: методы интегрального типа, такие как Math.min(int,int)
, на самом деле являются простым сравнением, поэтому я бы ожидал разницы нет).
Вы можете использовать тройной оператор следующим образом:
smallest=(a<b)?((a<c)?a:c):((b<c)?b:c);
Для этого требуется только одно назначение и минимум два сравнения.
Но я думаю, что эти утверждения не повлияли бы на время выполнения, ваша первоначальная логика займет то же самое время, что и мое и все остальные.
a
, b
и c
smallest
.
В эффективном коде OP есть ошибка:
когда a == b
и a (or b) < c
, код выберет c вместо a или b.
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c && b<a){
smallest = b;
}else{
smallest = c;
}
можно улучшить, чтобы:
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c){
smallest = b;
}else{
smallest = c;
}
Для эффективности чистых символов кода я не могу найти ничего лучше, чем
smallest = a<b&&a<c?a:b<c?b:c;
Если вы вызовете min() около 1kk раз с разными a, b, c, используйте мой метод:
Здесь только два сравнения. Невозможно ускорить вычисление: P
public static double min(double a, double b, double c) {
if (a > b) { //if true, min = b
if (b > c) { //if true, min = c
return c;
} else { //else min = b
return b;
}
} //else min = a
if (a > c) { // if true, min=c
return c;
} else {
return a;
}
}
Math.min
использует простое сравнение для выполнения своей задачи. Единственное преимущество не использовать Math.min - это сохранить дополнительные вызовы функций, но это незначительная экономия.
Если у вас есть не более трех чисел, метод minimum
для любого числа double
может быть ценным и выглядел бы примерно так:
public static double min(double ... numbers) {
double min = numbers[0];
for (int i=1 ; i<numbers.length ; i++) {
min = (min <= numbers[i]) ? min : numbers[i];
}
return min;
}
Для трех чисел это функциональный эквивалент Math.min(a, Math.min(b, c));
, но вы сохраняете один вызов метода.
Math.min
использует только простое сравнение для целочисленных типов данных. Но для double
и float
, он также заботится о специальных случаях, таких как NaN
.
Все выглядит нормально, ваш код будет в порядке, если вы не делаете это в плотном цикле. Я также рассмотрел бы
double min;
min = (a<b) ? a : b;
min = (min<c) ? min : c;
попробуйте для цикла. Я думаю, что это быстрее и удобнее при обновлении кода, например, если вы хотите добавить другое целое число для сравнения.
int lowestnum;
for(Integer i = 0; i < x; i++){
if(lowestnum == null || x < lowestnum){
lowestnum = x;
}
}
Для тех, кто находит эту тему намного позже:
Если у вас есть только три значения для сравнения, то нет существенной разницы. Но если вам нужно найти минимум, скажем, тридцати или шестидесяти значений, "min" может быть проще для любого прочитать код в следующем году:
int smallest;
smallest = min(a1, a2);
smallest = min(smallest, a3);
smallest = min(smallest, a4);
...
smallest = min(smallest, a37);
Но если вы думаете о скорости, возможно, лучший способ - поместить значения в список, а затем найти min из этого:
List<Integer> mySet = Arrays.asList(a1, a2, a3, ..., a37);
int smallest = Collections.min(mySet);
Согласны ли вы?
Collections.min()
поможет speed
процесс. Удача Java в «неявном параллелизме / параллелизме» - это stream
.
Я бы использовал min/max
(и не беспокоиться иначе)... однако, здесь есть еще один подход "длинной руки", который может или не может быть проще для некоторых людей понять. (Я бы не ожидал, что это будет быстрее или медленнее, чем код в сообщении.)
int smallest;
if (a < b) {
if (a > c) {
smallest = c;
} else { // a <= c
smallest = a;
}
} else { // a >= b
if (b > c) {
smallest = c;
} else { // b <= c
smallest = b;
}
}
Просто выбросьте его в микс.
Обратите внимание, что это только побочный вариант ответа Абхишека.
Напишите метод minimum3, который возвращает наименьшее из трех чисел с плавающей запятой. Используйте метод Math.min для реализации minimum3. Включите этот метод в приложение, которое считывает три значения от пользователя, определяет наименьшее значение и отображает результат.
Просто используйте эту математическую функцию
System.out.println(Math.min(a,b,c));
Вы получите ответ в одной строке.
Math.min
функции с Math.min
аргументами Math.min
. Прочитайте Javadocs.