担心我的 Web 应用程序的性能,我想知道“if/else”或 switch 语句中哪个在性能方面更好?
if
列表等来进行优化。
我完全同意过早优化是应该避免的观点。
但是 Java VM 确实具有可用于 switch() 的特殊字节码。
请参阅 WM Spec(lookupswitch 和 tableswitch)
因此,如果代码是性能 CPU 图的一部分,则可能会有一些性能提升。
那是微优化和过早优化,这是邪恶的。而是担心相关代码的可读性和可维护性。如果有两个以上的 if/else
块粘合在一起或其大小不可预测,那么您可能会高度考虑使用 switch
语句。
或者,您也可以获取多态性。首先创建一些界面:
public interface Action {
void execute(String input);
}
并掌握一些 Map
中的所有实现。您可以静态或动态地执行此操作:
Map<String, Action> actions = new HashMap<String, Action>();
最后将 if/else
或 switch
替换为类似这样的内容(将诸如空指针之类的琐碎检查放在一边):
actions.get(name).execute(input);
它可能比 if/else
或 switch
稍微慢一些,但至少代码的可维护性要好得多。
当您谈论 web 应用程序时,您可以使用 HttpServletRequest#getPathInfo()
作为操作键(最终编写更多代码以将 pathinfo 的最后一部分拆分为循环,直到找到操作)。你可以在这里找到类似的答案:
使用自定义的面向 Servlet 的框架,太多的 servlet,这是一个问题吗
Java 前端控制器
如果您总体上担心 Java EE Web 应用程序的性能,那么您可能会发现 this article 也很有用。与仅(微)优化原始 Java 代码相比,还有其他领域可以提供更多性能提升。
if/else 或 switch 极不可能成为性能问题的根源。如果您遇到性能问题,您应该首先进行性能分析分析以确定慢点在哪里。过早优化是万恶之源!
尽管如此,还是可以通过 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>
必须解析为整数表达式。
如果这个集合是“密集的”——即 (max(ci) + 1 - min(ci)) / n > α,其中 0 < k < α < 1,其中 k 大于某个经验值,跳转可以生成表,效率很高。
如果这个集合不是很密集,但是 n >= β,二叉搜索树可以在 O(2 * log(n)) 中找到目标,这仍然是有效的。
对于所有其他情况,switch 语句与等效的 if/else 语句系列一样有效。 α 和 β 的精确值取决于许多因素,并由编译器的代码优化模块确定。
最后,当然,如果 <condition>
的域不是整数,则 switch 语句完全没用。
使用开关!
我讨厌维护 if-else-blocks!做一个测试:
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));
}
}
My C# standard code for benchmarking
switch
es吗?
我记得读过 Java 字节码中有两种 Switch 语句。 (我认为它是在“Java 性能调优”中的一个是一个非常快速的实现,它使用 switch 语句的整数值来知道要执行的代码的偏移量。这将要求所有整数是连续的并且在明确定义的范围内.我猜测使用枚举的所有值也属于该类别。
不过,我同意许多其他发帖者的观点……担心这一点可能为时过早,除非这是非常热门的代码。
switch
几种不同的方式,其中一些方式比其他方式更有效。一般来说,效率不会比直接的“if
阶梯”差,但是有足够的变化(尤其是JITC),很难比这更精确。
根据 Cliff Click 在他的 2009 Java One 演讲 A Crash Course in Modern Hardware 中:
今天,性能由内存访问模式主导。缓存未命中占主导地位——内存是新磁盘。 [幻灯片 65]
您可以获取他的完整幻灯片here。
Cliff 给出了一个示例(在幻灯片 30 上完成),表明即使 CPU 进行寄存器重命名、分支预测和推测执行,它也只能在 4 个时钟周期内启动 7 个操作,然后由于两次缓存未命中而不得不阻塞,这需要300 个时钟周期返回。
所以他说要加速你的程序,你不应该关注这种小问题,而应该关注更大的问题,比如你是否进行了不必要的数据格式转换,比如转换“SOAP → XML → DOM → SQL → ... ”,它“通过缓存传递所有数据”。
在我的测试中,更好的性能是 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
*/
Time taken for String in Switch :3235 Time taken for String in if/else if :3143 Time taken for String in Map :4194 Time taken for String in ENUM :2866
对于大多数 switch
和大多数 if-then-else
块,我无法想象存在任何明显或重要的与性能相关的问题。
但事情是这样的:如果您使用的是 switch
块,那么它的用途表明您正在打开一个取自编译时已知的一组常量的值。在这种情况下,如果您可以将 enum
与特定于常量的方法一起使用,那么您真的根本不应该使用 switch
语句。
与 switch
语句相比,枚举提供了更好的类型安全性和更易于维护的代码。可以设计枚举,以便如果将常量添加到常量集中,如果不为新值提供特定于常量的方法,您的代码将无法编译。另一方面,忘记将新的 case
添加到 switch
块有时只能在运行时捕获,前提是您有幸将块设置为抛出异常。
switch
和 enum
常量特定方法之间的性能应该没有显着差异,但后者更具可读性、更安全且更易于维护。
下面的链接有很好的解释:
https://www.geeksforgeeks.org/switch-vs-else/
测试(c++17)
1 - 如果分组
2 - 如果顺序
3 - 转到数组
4 - 切换案例 - 跳转表
https://onlinegdb.com/Su7HNEBeG