我有一个用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;
}
或者,如果任何其他方式更有效
我想知道是否值得改变我目前正在使用的东西?
任何提速都会非常有帮助
答案 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中的微博目标是一门艺术,为了获得更可靠的结果,我们应该考虑使用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)
实际上 是一个简单的比较 - 所以我希望没有这些差异。)
答案 4 :(得分:4)
您可以按如下方式使用三元运算符:
smallest=(a<b)?((a<c)?a:c):((b<c)?b:c);
只需一次作业,最少两次比较。
但我认为这些陈述对执行时间没有任何影响,你的初始逻辑将与我和其他所有人一样。
答案 5 :(得分:3)
OP的高效代码有一个错误:
当a == b
和a (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);
答案 15 :(得分:-3)
编写一个方法minimum3,它返回三个浮点数中最小的一个。使用Math.min方法实现minimum3。将该方法合并到一个应用程序中,该应用程序从用户读取三个值,确定最小值并显示结果。
答案 16 :(得分:-4)
只需使用此数学函数
System.out.println(Math.min(a,b,c));
您将获得单行答案。