Java中if / else和switch语句的相对性能差异是什么?
担心我的Web应用程序的性能,我想知道哪个“if / else”或switch语句对性能更好?
这是微观优化和过早优化,这是邪恶的。 而是担心代码的可读性和可维护性。 如果两个以上的if/else
块粘在一起或者其大小是不可预知的,那么您可以高度考虑switch
语句。
另外,你也可以抓多态 。 首先创build一些界面:
public interface Action { void execute(String input); }
并掌握一些Map
的所有实现。 您可以静态或dynamic地执行此操作:
Map<String, Action> actions = new HashMap<String, Action>();
最后replaceif/else
或者像这样的switch
(除了像nullpointers这样的细小的检查):
actions.get(name).execute(input);
它可能比if/else
或switch
,但代码至less要好得多。
在讨论web应用程序时,可以使用HttpServletRequest#getPathInfo()
作为操作键(最后编写一些代码将pathinfo的最后一部分拆分成循环,直到find操作为止)。 你可以在这里find类似的答案:
- 使用定制的面向Servlet的框架,太多的servlet,这是一个问题
- Java前端控制器
如果您一般担心Java EE Web应用程序的性能,那么您可能会发现这篇文章也很有用。 还有其他一些领域比仅仅(微)优化原始Java代码提供了更多的性能增益。
我完全同意这样的观点,即过早的优化是可以避免的。
但是,Java VM确实有一些特殊的字节码可以用于switch()。
请参阅WM规格 ( lookupswitch和tableswitch )
因此,如果代码是性能CPU图的一部分,则可能会有一些性能提升。
if / else或switch将成为你的性能问题的根源是极不可能的。 如果您遇到性能问题,则应首先进行性能分析分析,以确定缓慢点的位置。 过早的优化是万恶之源!
尽pipe如此,可以通过Java编译器优化讨论switch和if / else的相对性能。 首先要注意的是,在Java中,switch语句在非常有限的域上运行 – 整数。 通常,您可以按如下方式查看switch语句:
switch (<condition>) { case c_0: ... case c_1: ... ... case c_n: ... default: ... }
其中c_0
, c_1
,…和c_N
是switch语句的目标的整数, <condition>
必须parsing为整数expression式。
-
如果这个集合是“密集的” – 即(max(c i )+ 1 – min(c i ))/ n>α,其中0 <k <α<1,其中
k
大于某个经验值,可以生成一个跳转表,效率很高。 -
如果这个集合不是很密集,但是n> =β,二叉search树可以在O(2 * log(n))中find目标,这个目标依然是有效的。
对于所有其他情况,switch语句与if / else语句的等效序列完全一样有效。 α和β的精确值取决于许多因素,由编译器的代码优化模块确定。
最后,当然,如果<condition>
的域不是整数,switch语句完全没用。
使用开关!
我讨厌维护if-else-blocks! 有一个testing:
public class SpeedTestSwitch { private static void do1(int loop) { int temp = 0; for (; loop > 0; --loop) { int r = (int) (Math.random() * 10); switch (r) { case 0: temp = 9; break; case 1: temp = 8; break; case 2: temp = 7; break; case 3: temp = 6; break; case 4: temp = 5; break; case 5: temp = 4; break; case 6: temp = 3; break; case 7: temp = 2; break; case 8: temp = 1; break; case 9: temp = 0; break; } } System.out.println("ignore: " + temp); } private static void do2(int loop) { int temp = 0; for (; loop > 0; --loop) { int r = (int) (Math.random() * 10); if (r == 0) temp = 9; else if (r == 1) temp = 8; else if (r == 2) temp = 7; else if (r == 3) temp = 6; else if (r == 4) temp = 5; else if (r == 5) temp = 4; else if (r == 6) temp = 3; else if (r == 7) temp = 2; else if (r == 8) temp = 1; else if (r == 9) temp = 0; } System.out.println("ignore: " + temp); } public static void main(String[] args) { long time; int loop = 1 * 100 * 1000 * 1000; System.out.println("warming up..."); do1(loop / 100); do2(loop / 100); System.out.println("start"); // run 1 System.out.println("switch:"); time = System.currentTimeMillis(); do1(loop); System.out.println(" -> time needed: " + (System.currentTimeMillis() - time)); // run 2 System.out.println("if/else:"); time = System.currentTimeMillis(); do2(loop); System.out.println(" -> time needed: " + (System.currentTimeMillis() - time)); } }
我的C#标准代码
根据Cliff在他2009年的Java One讲座中点击现代硬件中的速成课程 :
今天,性能主要受内存访问模式的影响。 caching未命中占主导地位 – 内存是新的磁盘。 [幻灯片65]
你可以在这里得到他的全部幻灯片。
Cliff给出了一个例子(在幻灯片30上完成),表明即使在CPU进行寄存器重命名,分支预测和推测执行的情况下,它也只能在4个时钟周期内开始7个操作,之后由于两次caching未命中300个时钟周期返回。
所以他说为了加快你的程序,你不应该看到这种小问题,而是在更大的问题上,比如是否进行不必要的数据格式转换,比如转换“SOAP→XML→DOM→SQL→…” “其中”通过caching传递所有数据“。
我记得读了Java字节码中有两种Switch语句。 (我认为它是在“Java Performance Tuning”中,一个是使用switch语句的整数值来知道要执行的代码的偏移的非常快的实现,这将要求所有的整数都是连续的,并且在一个明确的范围内我猜测使用Enum的所有值都会属于这个类别。
我同意许多其他的海报,现在担心这一点可能为时过早,除非这是非常热门的代码。
对于大多数switch
和大多数if-then-else
块,我无法想象有什么明显或显着的性能相关问题。
但是这里有一点:如果你使用了一个switch
块,它的使用意味着你正在从编译时已知的一组常量开始取值。 在这种情况下,如果可以使用具有常量特定方法的enum
,则实际上不应该使用switch
语句。
与switch
语句相比,枚举提供了更好的types安全性和代码,更易于维护。 枚举可以这样devise,如果一个常量被添加到常量集合中,那么如果不为新值提供一个特定于常量的方法,那么您的代码将不会被编译。 另一方面,如果你有足够的幸运来设置你的程序块来抛出一个exception,忘记给一个switch
块添加一个新的case
有时候只能在运行时被捕获。
switch
和enum
常量特定的方法之间的性能不应该有显着的不同,但后者更可读,更安全,更容易维护。
在我的testing中,更好的性能是在Windows7中ENUM> MAP> SWITCH> IF / ELSE IF 。
import java.util.HashMap; import java.util.Map; public class StringsInSwitch { public static void main(String[] args) { String doSomething = null; //METHOD_1 : SWITCH long start = System.currentTimeMillis(); for (int i = 0; i < 99999999; i++) { String input = "Hello World" + (i & 0xF); switch (input) { case "Hello World0": doSomething = "Hello World0"; break; case "Hello World1": doSomething = "Hello World0"; break; case "Hello World2": doSomething = "Hello World0"; break; case "Hello World3": doSomething = "Hello World0"; break; case "Hello World4": doSomething = "Hello World0"; break; case "Hello World5": doSomething = "Hello World0"; break; case "Hello World6": doSomething = "Hello World0"; break; case "Hello World7": doSomething = "Hello World0"; break; case "Hello World8": doSomething = "Hello World0"; break; case "Hello World9": doSomething = "Hello World0"; break; case "Hello World10": doSomething = "Hello World0"; break; case "Hello World11": doSomething = "Hello World0"; break; case "Hello World12": doSomething = "Hello World0"; break; case "Hello World13": doSomething = "Hello World0"; break; case "Hello World14": doSomething = "Hello World0"; break; case "Hello World15": doSomething = "Hello World0"; break; } } System.out.println("Time taken for String in Switch :"+ (System.currentTimeMillis() - start)); //METHOD_2 : IF/ELSE IF start = System.currentTimeMillis(); for (int i = 0; i < 99999999; i++) { String input = "Hello World" + (i & 0xF); if(input.equals("Hello World0")){ doSomething = "Hello World0"; } else if(input.equals("Hello World1")){ doSomething = "Hello World0"; } else if(input.equals("Hello World2")){ doSomething = "Hello World0"; } else if(input.equals("Hello World3")){ doSomething = "Hello World0"; } else if(input.equals("Hello World4")){ doSomething = "Hello World0"; } else if(input.equals("Hello World5")){ doSomething = "Hello World0"; } else if(input.equals("Hello World6")){ doSomething = "Hello World0"; } else if(input.equals("Hello World7")){ doSomething = "Hello World0"; } else if(input.equals("Hello World8")){ doSomething = "Hello World0"; } else if(input.equals("Hello World9")){ doSomething = "Hello World0"; } else if(input.equals("Hello World10")){ doSomething = "Hello World0"; } else if(input.equals("Hello World11")){ doSomething = "Hello World0"; } else if(input.equals("Hello World12")){ doSomething = "Hello World0"; } else if(input.equals("Hello World13")){ doSomething = "Hello World0"; } else if(input.equals("Hello World14")){ doSomething = "Hello World0"; } else if(input.equals("Hello World15")){ doSomething = "Hello World0"; } } System.out.println("Time taken for String in if/else if :"+ (System.currentTimeMillis() - start)); //METHOD_3 : MAP //Create and build Map Map<String, ExecutableClass> map = new HashMap<String, ExecutableClass>(); for (int i = 0; i <= 15; i++) { String input = "Hello World" + (i & 0xF); map.put(input, new ExecutableClass(){ public void execute(String doSomething){ doSomething = "Hello World0"; } }); } //Start test map start = System.currentTimeMillis(); for (int i = 0; i < 99999999; i++) { String input = "Hello World" + (i & 0xF); map.get(input).execute(doSomething); } System.out.println("Time taken for String in Map :"+ (System.currentTimeMillis() - start)); //METHOD_4 : ENUM (This doesn't use muliple string with space.) start = System.currentTimeMillis(); for (int i = 0; i < 99999999; i++) { String input = "HW" + (i & 0xF); HelloWorld.valueOf(input).execute(doSomething); } System.out.println("Time taken for String in ENUM :"+ (System.currentTimeMillis() - start)); } } interface ExecutableClass { public void execute(String doSomething); } // Enum version enum HelloWorld { HW0("Hello World0"), HW1("Hello World1"), HW2("Hello World2"), HW3( "Hello World3"), HW4("Hello World4"), HW5("Hello World5"), HW6( "Hello World6"), HW7("Hello World7"), HW8("Hello World8"), HW9( "Hello World9"), HW10("Hello World10"), HW11("Hello World11"), HW12( "Hello World12"), HW13("Hello World13"), HW14("Hello World4"), HW15( "Hello World15"); private String name = null; private HelloWorld(String name) { this.name = name; } public String getName() { return name; } public void execute(String doSomething){ doSomething = "Hello World0"; } public static HelloWorld fromString(String input) { for (HelloWorld hw : HelloWorld.values()) { if (input.equals(hw.getName())) { return hw; } } return null; } } //Enum version for betterment on coding format compare to interface ExecutableClass enum HelloWorld1 { HW0("Hello World0") { public void execute(String doSomething){ doSomething = "Hello World0"; } }, HW1("Hello World1"){ public void execute(String doSomething){ doSomething = "Hello World0"; } }; private String name = null; private HelloWorld1(String name) { this.name = name; } public String getName() { return name; } public void execute(String doSomething){ // super call, nothing here } } /* * http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10 * http://forums.xkcd.com/viewtopic.php?f=11&t=33524 */