I have an algorithm written in Java that I would like to make more efficient. A part that I think could be made more efficient is finding the smallest of 3 numbers. Currently I'm using the Math.min
method as below:
我有一个用Java编写的算法,我想让它更高效。我认为可以提高效率的部分是找到3个数字中最小的一个。现在我在用数学。最小值方法如下:
double smallest = Math.min(a, Math.min(b, c));
How efficient is this? Would it be more efficient to replace with if statements like below:
这是如何高效?如果下列语句取代if语句是否更有效:
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c && b<a){
smallest = b;
}else{
smallest = c;
}
Or if any other way is more efficient
或者其他更有效的方法
I'm wondering if it is worth changing what I'm currently using?
我想知道我现在用的东西是否值得改变?
Any speed increase would be greatly helpful
任何速度的提高都会有很大的帮助。
15 个解决方案
#1
19
No, it's seriously not worth changing. The sort of improvements you're going to get when fiddling with micro-optimisations like this will not be worth it. Even the method call cost will be removed if the min
function is called enough.
不,真的不值得改变。当摆弄这些微小的优化时,你会得到的改进是不值得的。即使调用了足够的min函数,也会删除方法调用成本。
If you have a problem with your algorithm, your best bet is to look into macro-optimisations ("big picture" stuff like algorithm selection or tuning) - you'll generally get much better performance improvements there.
如果你的算法有问题,你最好的办法是研究宏观优化(“全局”的东西,比如算法选择或优化)——你通常会得到更好的性能改进。
And your comment that removing Math.pow
gave improvements may well be correct but that's because it's a relatively expensive operation. Math.min
will not even be close to that in terms of cost.
还有你关于去掉数学的评论。pow的改进可能是正确的,但那是因为这是一个相对昂贵的操作。数学。最小值在成本上甚至都不会接近那个值。
#2
23
For a lot of utility-type methods, the apache commons libraries have solid implementations that you can either leverage or get additional insight from. In this case, there is a method for finding the smallest of three doubles available in org.apache.commons.lang.math.NumberUtils. Their implementation is actually nearly identical to your initial thought:
对于许多实用程序类型的方法,apache commons库具有可靠的实现,您可以利用这些实现,也可以从这些实现中获得更多的见解。在这种情况下,有一个方法可以在org.apache.common .lang.math. numberutils中找到三倍中最小的一个。它们的实现与您最初的想法几乎完全相同:
public static double min(double a, double b, double c) {
return Math.min(Math.min(a, b), c);
}
#3
14
double smallest = a;
if (smallest > b) smallest = b;
if (smallest > c) smallest = c;
Not necessarily faster than your code.
不一定比代码快。
#4
6
Let me first repeat what others already said, by quoting from the article "Structured Programming with go to Statements" by Donald Knuth:
让我首先重复一下其他人已经说过的话,引用Donald Knuth的文章“结构化编程与go to Statements”:
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
我们应该忘记小的效率,大约97%的时间:过早的优化是所有罪恶的根源。
Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.
然而,我们不应该在关键的3%中放弃我们的机会。一个好的程序员不会因为这样的推理而沾沾自喜,他会明智地仔细查看关键的代码;但只有在确定了这些代码之后。
(emphasis by me)
(强调我)
So if you have identified that a seemingly trivial operation like the computation of the minimum of three numbers is the actual bottleneck (that is, the "critical 3%") in your application, then you may consider optimizing it.
因此,如果您已经确定了一个看似微不足道的操作(如计算最少三个数字)是应用程序中的实际瓶颈(即“临界3%”),那么您可以考虑对其进行优化。
And in this case, this is actually possible: The Math#min(double,double)
method in Java has very special semantics:
在这种情况下,这实际上是可能的:Java中的Math#min(double,double)方法具有非常特殊的语义:
Returns the smaller of two double values. That is, the result is the value closer to negative infinity. If the arguments have the same value, the result is that same value. If either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this method considers negative zero to be strictly smaller than positive zero. If one argument is positive zero and the other is negative zero, the result is negative zero.
返回两个双值中较小的值。也就是说,结果是接近负无穷。如果参数具有相同的值,那么结果就是相同的值。如果任一值为NaN,则结果为NaN。与数值比较算符不同,该方法认为负零严格小于正零。如果一个参数是正的,另一个是负的,结果是负的。
One can have a look at the implementation, and see that it's actually rather complex:
我们可以看看这个实现,它实际上是相当复杂的:
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;
}
Now, it may be important to point out that this behavior is different from a simple comparison. This can easily be examined with the following example:
现在,有必要指出,这种行为与简单的比较是不同的。可以通过以下示例轻松地检查这一点:
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();
}
}
However: If the treatment of NaN
and positive/negative zero is not relevant for your application, you can replace the solution that is based on Math.min
with a solution that is based on a simple comparison, and see whether it makes a difference.
但是:如果NaN和正值/负值的处理与您的应用程序无关,您可以替换基于Math的解决方案。以一个简单的比较为基础的解决方案的最小值,看看它是否会产生影响。
This will, of course, be application dependent. Here is a simple, artificial microbenchmark (to be taken with a grain of salt!)
当然,这将依赖于应用程序。这里有一个简单的,人工的微基准测试(要带点怀疑!)
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;
}
}
(Disclaimer: Microbenchmarking in Java is an art, and for more reliable results, one should consider using JMH or Caliper).
(声明:Java中的微基准测试是一门艺术,为了获得更可靠的结果,应该考虑使用JMH或Caliper)。
Running this with JRE 1.8.0_31 may result in something like
使用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
This at least suggests that it might be possible to squeeze out a few percent here (again, in a very artifical example).
这至少表明,在这里挤出几个百分点是可能的(同样,在一个非常巧妙的例子中)。
Analyzing this further, by looking at the hotspot disassembly output created with
进一步分析,通过查看用hotspot创建的分解输出
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MinPerformance
one can see the optimized versions of both methods, minA
and minB
.
可以看到两种方法的优化版本,minA和minB。
First, the output for the method that uses Math.min
:
首先,使用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
One can see that the treatment of special cases involves some effort - compared to the output that uses simple comparisons, which is rather straightforward:
我们可以看到,处理特殊情况需要付出一些努力——与使用简单比较的输出相比,这是相当简单的:
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
Whether or not there are cases where such an optimization really makes a difference in an application is hard to tell. But at least, the bottom line is:
这种优化是否在应用程序中真正起作用的情况很难判断。但至少,底线是:
- The
Math#min(double,double)
method is not the same as a simple comparison, and the treatment of the special cases does not come for free - 数学#min(double,double)方法不同于简单的比较,特殊情况的处理也不是免费的
- There are cases where the special case treatment that is done by
Math#min
is not necessary, and then a comparison-based approach may be more efficient - 有些情况下,不需要使用Math#min执行的特殊情况处理,然后使用基于比较的方法可能会更有效
- As already pointed out in other answers: In most cases, the performance difference will not matter. However, for this particular example, one should probably create a utility method
min(double,double,double)
anyhow, for better convenience and readability, and then it would be easy to do two runs with the different implementations, and see whether it really affects the performance. - 正如在其他答案中已经指出的:在大多数情况下,性能差异并不重要。但是,对于这个特定的示例,您可能应该创建一个实用方法min(double,double,double),以便更好地方便和可读性,然后使用不同的实现进行两次运行,看看它是否真正影响性能。
(Side note: The integral type methods, like Math.min(int,int)
actually are a simple comparison - so I would expect no difference for these).
(附注:整数类型的方法,如Math.min(int,int)实际上是一个简单的比较,所以我认为这些方法没有区别)。
#5
4
You can use ternary operator as follows:
你可以使用三元运算符如下:
smallest=(a<b)?((a<c)?a:c):((b<c)?b:c);
Which takes only one assignment and minimum two comparisons.
它只需要一个赋值和最少两个比较。
But I think that these statements would not have any effect on execution time, your initial logic will take same time as of mine and all of others.
但是我认为这些陈述不会对执行时间产生任何影响,你最初的逻辑将会和我和其他所有人一样花费同样的时间。
#6
2
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c && b<a){
smallest = b;
}else{
smallest = c;
}
can be improved to:
可以改善:
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c){
smallest = b;
}else{
smallest = c;
}
#7
2
OP's efficient code has a bug:
OP的高效代码有一个缺陷:
when a == b
, and a (or b) < c
, the code will pick c instead of a or b.
当a = b, a(或b) < c时,代码会选择c而不是a或b。
#8
1
It all looks ok, your code will be fine, unless you're doing this in a tight loop. I also would consider
这一切看起来都很好,您的代码将会很好,除非您在一个紧密的循环中这样做。我也会考虑
double min;
min = (a<b) ? a : b;
min = (min<c) ? min : c;
#9
1
Math.min
uses a simple comparison to do its thing. The only advantage to not using Math.min is to save the extra function calls, but that is a negligible saving.
数学。min使用一个简单的比较来做它的事情。不使用数学的唯一好处。最小值是保存额外的函数调用,但这是可以忽略的。
If you have more than just three numbers, having a minimum
method for any number of double
s might be valuable and would look something like:
如果你有三个以上的数字,有一个最小的方法,任何数目的双打都可能是有价值的,看起来像:
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;
}
For three numbers this is the functional equivalent of Math.min(a, Math.min(b, c));
but you save one method invocation.
对于三个数字,这是数学的函数。分钟(a,数学。最小值(b,c));但是保存一个方法调用。
#10
1
If you will call min() around 1kk times with different a, b, c, then use my method:
如果你用不同的a, b, c调用min()大约1kk次,那么使用我的方法:
Here only two comparisons. There is no way to calc faster :P
这里只有两个比较。没有办法更快地计算: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
For pure characters-of-code efficiency, I can't find anything better than
对于纯粹的代码字符效率,我找不到比这更好的了
smallest = a<b&&a<c?a:b<c?b:c;
#12
0
I would use min/max
(and not worry otherwise) ... however, here is another "long hand" approach which may or may not be easier for some people to understand. (I would not expect it to be faster or slower than the code in the post.)
我会用最小/最大值(不用担心)……然而,这里还有另一种“长手”的方法,有些人可能更容易理解。(我不认为它会比文章中的代码快或慢。)
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;
}
}
Just throwing it into the mix.
只是把它混合在一起。
Note that this is just the side-effecting variant of Abhishek's answer.
请注意,这只是阿布舍克的回答的副作用。
#13
0
For those who find this topic much later:
对于那些后来发现这个话题的人:
If you have just three values to compare there is no significant difference. But if you have to find min of, say, thirty or sixty values, "min" could be easier for anyone to read in the code next year:
如果你只有三个值来比较,没有显著的差别。但是如果你必须找到最小值,比如说,30或60个值,“最小值”可以让任何人更容易在明年的代码中读到:
int smallest;
smallest = min(a1, a2);
smallest = min(smallest, a3);
smallest = min(smallest, a4);
...
smallest = min(smallest, a37);
But if you think of speed, maybe better way would be to put values into list, and then find min of that:
但是如果你考虑速度,也许更好的方法是把值放到列表中,然后找到最小值:
List<Integer> mySet = Arrays.asList(a1, a2, a3, ..., a37);
int smallest = Collections.min(mySet);
Would you agree?
你会同意吗?
#14
-3
Write a method minimum3 that returns the smallest of three floating-point numbers. Use the Math.min method to implement minimum3. Incorporate the method into an application that reads three values from the user, determines the smallest value and displays the result.
编写一个方法minimum3,它返回三个浮点数中的最小值。使用数学。最小方法实现最小3。将该方法合并到一个应用程序中,该应用程序从用户读取三个值,确定最小值并显示结果。
#15
-4
Simply use this math function
只需使用这个数学函数
System.out.println(Math.min(a,b,c));
You will get the answer in single line.
你会得到单行的答案。
#1
19
No, it's seriously not worth changing. The sort of improvements you're going to get when fiddling with micro-optimisations like this will not be worth it. Even the method call cost will be removed if the min
function is called enough.
不,真的不值得改变。当摆弄这些微小的优化时,你会得到的改进是不值得的。即使调用了足够的min函数,也会删除方法调用成本。
If you have a problem with your algorithm, your best bet is to look into macro-optimisations ("big picture" stuff like algorithm selection or tuning) - you'll generally get much better performance improvements there.
如果你的算法有问题,你最好的办法是研究宏观优化(“全局”的东西,比如算法选择或优化)——你通常会得到更好的性能改进。
And your comment that removing Math.pow
gave improvements may well be correct but that's because it's a relatively expensive operation. Math.min
will not even be close to that in terms of cost.
还有你关于去掉数学的评论。pow的改进可能是正确的,但那是因为这是一个相对昂贵的操作。数学。最小值在成本上甚至都不会接近那个值。
#2
23
For a lot of utility-type methods, the apache commons libraries have solid implementations that you can either leverage or get additional insight from. In this case, there is a method for finding the smallest of three doubles available in org.apache.commons.lang.math.NumberUtils. Their implementation is actually nearly identical to your initial thought:
对于许多实用程序类型的方法,apache commons库具有可靠的实现,您可以利用这些实现,也可以从这些实现中获得更多的见解。在这种情况下,有一个方法可以在org.apache.common .lang.math. numberutils中找到三倍中最小的一个。它们的实现与您最初的想法几乎完全相同:
public static double min(double a, double b, double c) {
return Math.min(Math.min(a, b), c);
}
#3
14
double smallest = a;
if (smallest > b) smallest = b;
if (smallest > c) smallest = c;
Not necessarily faster than your code.
不一定比代码快。
#4
6
Let me first repeat what others already said, by quoting from the article "Structured Programming with go to Statements" by Donald Knuth:
让我首先重复一下其他人已经说过的话,引用Donald Knuth的文章“结构化编程与go to Statements”:
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
我们应该忘记小的效率,大约97%的时间:过早的优化是所有罪恶的根源。
Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.
然而,我们不应该在关键的3%中放弃我们的机会。一个好的程序员不会因为这样的推理而沾沾自喜,他会明智地仔细查看关键的代码;但只有在确定了这些代码之后。
(emphasis by me)
(强调我)
So if you have identified that a seemingly trivial operation like the computation of the minimum of three numbers is the actual bottleneck (that is, the "critical 3%") in your application, then you may consider optimizing it.
因此,如果您已经确定了一个看似微不足道的操作(如计算最少三个数字)是应用程序中的实际瓶颈(即“临界3%”),那么您可以考虑对其进行优化。
And in this case, this is actually possible: The Math#min(double,double)
method in Java has very special semantics:
在这种情况下,这实际上是可能的:Java中的Math#min(double,double)方法具有非常特殊的语义:
Returns the smaller of two double values. That is, the result is the value closer to negative infinity. If the arguments have the same value, the result is that same value. If either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this method considers negative zero to be strictly smaller than positive zero. If one argument is positive zero and the other is negative zero, the result is negative zero.
返回两个双值中较小的值。也就是说,结果是接近负无穷。如果参数具有相同的值,那么结果就是相同的值。如果任一值为NaN,则结果为NaN。与数值比较算符不同,该方法认为负零严格小于正零。如果一个参数是正的,另一个是负的,结果是负的。
One can have a look at the implementation, and see that it's actually rather complex:
我们可以看看这个实现,它实际上是相当复杂的:
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;
}
Now, it may be important to point out that this behavior is different from a simple comparison. This can easily be examined with the following example:
现在,有必要指出,这种行为与简单的比较是不同的。可以通过以下示例轻松地检查这一点:
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();
}
}
However: If the treatment of NaN
and positive/negative zero is not relevant for your application, you can replace the solution that is based on Math.min
with a solution that is based on a simple comparison, and see whether it makes a difference.
但是:如果NaN和正值/负值的处理与您的应用程序无关,您可以替换基于Math的解决方案。以一个简单的比较为基础的解决方案的最小值,看看它是否会产生影响。
This will, of course, be application dependent. Here is a simple, artificial microbenchmark (to be taken with a grain of salt!)
当然,这将依赖于应用程序。这里有一个简单的,人工的微基准测试(要带点怀疑!)
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;
}
}
(Disclaimer: Microbenchmarking in Java is an art, and for more reliable results, one should consider using JMH or Caliper).
(声明:Java中的微基准测试是一门艺术,为了获得更可靠的结果,应该考虑使用JMH或Caliper)。
Running this with JRE 1.8.0_31 may result in something like
使用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
This at least suggests that it might be possible to squeeze out a few percent here (again, in a very artifical example).
这至少表明,在这里挤出几个百分点是可能的(同样,在一个非常巧妙的例子中)。
Analyzing this further, by looking at the hotspot disassembly output created with
进一步分析,通过查看用hotspot创建的分解输出
java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MinPerformance
one can see the optimized versions of both methods, minA
and minB
.
可以看到两种方法的优化版本,minA和minB。
First, the output for the method that uses Math.min
:
首先,使用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
One can see that the treatment of special cases involves some effort - compared to the output that uses simple comparisons, which is rather straightforward:
我们可以看到,处理特殊情况需要付出一些努力——与使用简单比较的输出相比,这是相当简单的:
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
Whether or not there are cases where such an optimization really makes a difference in an application is hard to tell. But at least, the bottom line is:
这种优化是否在应用程序中真正起作用的情况很难判断。但至少,底线是:
- The
Math#min(double,double)
method is not the same as a simple comparison, and the treatment of the special cases does not come for free - 数学#min(double,double)方法不同于简单的比较,特殊情况的处理也不是免费的
- There are cases where the special case treatment that is done by
Math#min
is not necessary, and then a comparison-based approach may be more efficient - 有些情况下,不需要使用Math#min执行的特殊情况处理,然后使用基于比较的方法可能会更有效
- As already pointed out in other answers: In most cases, the performance difference will not matter. However, for this particular example, one should probably create a utility method
min(double,double,double)
anyhow, for better convenience and readability, and then it would be easy to do two runs with the different implementations, and see whether it really affects the performance. - 正如在其他答案中已经指出的:在大多数情况下,性能差异并不重要。但是,对于这个特定的示例,您可能应该创建一个实用方法min(double,double,double),以便更好地方便和可读性,然后使用不同的实现进行两次运行,看看它是否真正影响性能。
(Side note: The integral type methods, like Math.min(int,int)
actually are a simple comparison - so I would expect no difference for these).
(附注:整数类型的方法,如Math.min(int,int)实际上是一个简单的比较,所以我认为这些方法没有区别)。
#5
4
You can use ternary operator as follows:
你可以使用三元运算符如下:
smallest=(a<b)?((a<c)?a:c):((b<c)?b:c);
Which takes only one assignment and minimum two comparisons.
它只需要一个赋值和最少两个比较。
But I think that these statements would not have any effect on execution time, your initial logic will take same time as of mine and all of others.
但是我认为这些陈述不会对执行时间产生任何影响,你最初的逻辑将会和我和其他所有人一样花费同样的时间。
#6
2
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c && b<a){
smallest = b;
}else{
smallest = c;
}
can be improved to:
可以改善:
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c){
smallest = b;
}else{
smallest = c;
}
#7
2
OP's efficient code has a bug:
OP的高效代码有一个缺陷:
when a == b
, and a (or b) < c
, the code will pick c instead of a or b.
当a = b, a(或b) < c时,代码会选择c而不是a或b。
#8
1
It all looks ok, your code will be fine, unless you're doing this in a tight loop. I also would consider
这一切看起来都很好,您的代码将会很好,除非您在一个紧密的循环中这样做。我也会考虑
double min;
min = (a<b) ? a : b;
min = (min<c) ? min : c;
#9
1
Math.min
uses a simple comparison to do its thing. The only advantage to not using Math.min is to save the extra function calls, but that is a negligible saving.
数学。min使用一个简单的比较来做它的事情。不使用数学的唯一好处。最小值是保存额外的函数调用,但这是可以忽略的。
If you have more than just three numbers, having a minimum
method for any number of double
s might be valuable and would look something like:
如果你有三个以上的数字,有一个最小的方法,任何数目的双打都可能是有价值的,看起来像:
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;
}
For three numbers this is the functional equivalent of Math.min(a, Math.min(b, c));
but you save one method invocation.
对于三个数字,这是数学的函数。分钟(a,数学。最小值(b,c));但是保存一个方法调用。
#10
1
If you will call min() around 1kk times with different a, b, c, then use my method:
如果你用不同的a, b, c调用min()大约1kk次,那么使用我的方法:
Here only two comparisons. There is no way to calc faster :P
这里只有两个比较。没有办法更快地计算: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
For pure characters-of-code efficiency, I can't find anything better than
对于纯粹的代码字符效率,我找不到比这更好的了
smallest = a<b&&a<c?a:b<c?b:c;
#12
0
I would use min/max
(and not worry otherwise) ... however, here is another "long hand" approach which may or may not be easier for some people to understand. (I would not expect it to be faster or slower than the code in the post.)
我会用最小/最大值(不用担心)……然而,这里还有另一种“长手”的方法,有些人可能更容易理解。(我不认为它会比文章中的代码快或慢。)
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;
}
}
Just throwing it into the mix.
只是把它混合在一起。
Note that this is just the side-effecting variant of Abhishek's answer.
请注意,这只是阿布舍克的回答的副作用。
#13
0
For those who find this topic much later:
对于那些后来发现这个话题的人:
If you have just three values to compare there is no significant difference. But if you have to find min of, say, thirty or sixty values, "min" could be easier for anyone to read in the code next year:
如果你只有三个值来比较,没有显著的差别。但是如果你必须找到最小值,比如说,30或60个值,“最小值”可以让任何人更容易在明年的代码中读到:
int smallest;
smallest = min(a1, a2);
smallest = min(smallest, a3);
smallest = min(smallest, a4);
...
smallest = min(smallest, a37);
But if you think of speed, maybe better way would be to put values into list, and then find min of that:
但是如果你考虑速度,也许更好的方法是把值放到列表中,然后找到最小值:
List<Integer> mySet = Arrays.asList(a1, a2, a3, ..., a37);
int smallest = Collections.min(mySet);
Would you agree?
你会同意吗?
#14
-3
Write a method minimum3 that returns the smallest of three floating-point numbers. Use the Math.min method to implement minimum3. Incorporate the method into an application that reads three values from the user, determines the smallest value and displays the result.
编写一个方法minimum3,它返回三个浮点数中的最小值。使用数学。最小方法实现最小3。将该方法合并到一个应用程序中,该应用程序从用户读取三个值,确定最小值并显示结果。
#15
-4
Simply use this math function
只需使用这个数学函数
System.out.println(Math.min(a,b,c));
You will get the answer in single line.
你会得到单行的答案。