找到最小的3个数字Java最有效的方法?

时间:2012-03-06 01:19:03

标签: java performance algorithm min

我有一个用Java编写的算法,我想提高效率。我认为可以提高效率的一个部分是找到3个数字中最小的一个。目前我正在使用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;
}

或者,如果任何其他方式更有效

我想知道是否值得改变我目前正在使用的东西?

任何提速都会非常有帮助

17 个答案:

答案 0 :(得分:26)

对于许多实用程序类型的方法,apache commons库具有可以利用或获得额外洞察力的可靠实现。在这种情况下,有一种方法可以找到org.apache.commons.lang.math.NumberUtils中可用的三个双打中最小的一个。它们的实现几乎与您最初的想法相同:

public static double min(double a, double b, double c) {
    return Math.min(Math.min(a, b), c);
}

答案 1 :(得分:22)

不,它严重值得改变。当摆弄这样的微优化时,你将获得的那种改进是不值得的。如果足够调用min函数,即使方法调用成本也将被删除。

如果您的算法有问题,最好的办法是研究宏优化(算法选择或调整等“大图片”) - 您通常会获得很多更好的性能那里的改进。

您对删除Math.pow提出改进的评​​论可能是正确的,但那是因为这是一项相对昂贵的操作。 Math.min在成本方面甚至不会接近。

答案 2 :(得分:16)

double smallest = a;
if (smallest > b) smallest = b;
if (smallest > c) smallest = c;

不一定比你的代码更快。

答案 3 :(得分:8)

让我先重复其他人已经说过的内容,引用文章&#34;结构化编程,转到语句&#34;作者:Donald Knuth:

  

我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。

     

然而,我们不应该在那个关键的3%中放弃我们的机会。一个好的程序员不会因为这样的推理而自满,他会明智地仔细研究关键代码;但只有在确定了该代码之后才会这样做。

(我强调)

因此,如果您已经确定一个看似微不足道的操作,例如计算三个数字的最小值实际瓶颈(即&#34;关键3%&#34;)在您的应用程序中,您可以考虑优化它。

在这种情况下,实际上这是可能的:Java中的Math#min(double,double)方法具有非常特殊的语义:

  

返回两个double值中较小的一个。也就是说,结果是值更接近负无穷大。如果参数具有相同的值,则结果是相同的值。如果任一值为NaN,则结果为NaN。与数值比较运算符不同,此方法将负零视为严格小于正零。如果一个参数为正零而另一个参数为负零,则结果为负零。

可以看一下实现,看看它实际上相当复杂:

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

(免责声明:Java中的微博目标是一门艺术,为了获得更可靠的结果,我们应该考虑使用JMHCaliper)。

使用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

可以看到这两种方法的优化版本minAminB

首先,使用Math.min的方法的输出:

Decoding compiled method 0x0000000002992310:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x000000001c010910} &apos;minA&apos; &apos;(DDD)D&apos; in &apos;MinPerformance&apos;
  # 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} &apos;minA&apos; &apos;(DDD)D&apos; in &apos;MinPerformance&apos;)}
  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} &apos;minB&apos; &apos;(DDD)D&apos; in &apos;MinPerformance&apos;
  # 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)实际上 是一个简单的比较 - 所以我希望没有这些差异。)

答案 4 :(得分:4)

您可以按如下方式使用三元运算符:

smallest=(a<b)?((a<c)?a:c):((b<c)?b:c);

只需一次作业,最少两次比较。

但我认为这些陈述对执行时间没有任何影响,你的初始逻辑将与我和其他所有人一样。

答案 5 :(得分:3)

OP的高效代码有一个错误:

a == ba (or b) < c时,代码将选择c而不是a或b。

答案 6 :(得分:2)

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

答案 7 :(得分:1)

一切看起来都不错,你的代码会很好,除非你在紧密循环中这样做。我也会考虑

double min;
min = (a<b) ? a : b;
min = (min<c) ? min : c;

答案 8 :(得分:1)

对于那些稍后发现这个话题的人:

如果您只有三个值进行比较,则没有显着差异。但是,如果你必须找到三十或六十的最小值,那么#34; min&#34;可能更容易让任何人明年阅读代码:

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);
你同意吗?

答案 9 :(得分:1)

对于纯粹的代码效率字符,我找不到比

更好的东西
smallest = a<b&&a<c?a:b<c?b:c;

答案 10 :(得分:1)

如果您使用不同的a,b,c调用min()大约1kk次,那么请使用我的方法:

这里只有两个比较。没有办法更快地计算: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;
    }
}

答案 11 :(得分:1)

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));的功能等价物,但是您保存了一个方法调用。

答案 12 :(得分:0)

我会使用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;
  }
}

把它扔进混合物中。

请注意,这只是Abhishek答案的副作用变种。

答案 13 :(得分:0)

可使用任意数量的输入值:

 public static double min(double... doubles) {
    double m = Double.MAX_VALUE;
    for (double d : doubles) {
        m = Math.min(m, d);
    }
    return m;
}

答案 14 :(得分:0)

使用Arrays.sort()方法,最小值为element0。

就性能而言,这应该并不昂贵,因为排序操作是 已经优化。还具有简洁的优点。

private int min(int ... value) {
    Arrays.sort(value);
    return value[0];
}

概念证明

    int[] intArr = {12, 5, 6, 9, 44, 28, 1, 4, 18, 2, 66, 13, 1, 33, 74, 12, 
    5, 6, 9, 44, 28, 1, 4, 18, 2, 66, 13};

    // Sorting approach
    long startTime = System.currentTimeMillis();
    int minVal = min(intArr);
    long endTime = System.currentTimeMillis();
    System.out.println("Sorting: Min => " + minVal + " took => " + (endTime - 
    startTime));
    System.out.println(startTime + "   "  + endTime);
    System.out.println(" ");

    // Scanning approach
    minVal = 100;
    startTime = System.currentTimeMillis();
    for(int val : intArr) {
        if (val < minVal)
            minVal = val;
    }
    endTime = System.currentTimeMillis();
    System.out.println("Iterating: Min => " + minVal + " took => " + (endTime 
    - startTime));
    System.out.println(startTime + "   "  + endTime);

enter image description here

答案 15 :(得分:-3)

编写一个方法minimum3,它返回三个浮点数中最小的一个。使用Math.min方法实现minimum3。将该方法合并到一个应用程序中,该应用程序从用户读取三个值,确定最小值并显示结果。

答案 16 :(得分:-4)

只需使用此数学函数

System.out.println(Math.min(a,b,c));

您将获得单行答案。