내 웹 응용 프로그램의 성능에 대해 걱정하면서 성능과 관련하여 "if / else"또는 switch 문 중 어느 것이 더 나은지 궁금합니다.
if
등
내 웹 응용 프로그램의 성능에 대해 걱정하면서 성능과 관련하여 "if / else"또는 switch 문 중 어느 것이 더 나은지 궁금합니다.
if
등
답변:
그것은 사악한 마이크로 최적화와 조기 최적화입니다. 오히려 해당 코드의 가독성과 유지 관리 가능성에 대해 걱정하십시오. 두 개 이상의 if/else
블록이 서로 붙어 있거나 크기를 예측할 수없는 경우 switch
진술을 고려할 수 있습니다 .
또는 Polymorphism 을 가져올 수도 있습니다 . 먼저 인터페이스를 만듭니다.
public interface Action {
void execute(String input);
}
그리고 일부 Map
. 이 작업은 정적으로 또는 동적으로 수행 할 수 있습니다.
Map<String, Action> actions = new HashMap<String, Action>();
마지막으로 if/else
or switch
를 다음과 같이 대체합니다 (널 포인터와 같은 사소한 검사는 제쳐두고) :
actions.get(name).execute(input);
그것은 수 보다 microslower 일 if/else
이나 switch
,하지만 코드는 적어도 훨씬 더 유지 보수입니다.
웹 응용 프로그램에 대해 이야기 할 HttpServletRequest#getPathInfo()
때 작업 키로 사용할 수 있습니다 (결국 작업이 발견 될 때까지 경로 정보의 마지막 부분을 루프에서 분리하는 코드를 더 작성). 여기에서 비슷한 답변을 찾을 수 있습니다.
일반적으로 Java EE 웹 애플리케이션 성능에 대해 걱정하고 있다면 이 기사도 유용 할 것입니다. 원시 Java 코드 만 (마이크로) 최적화하는 것보다 훨씬 더 많은 성능 향상 을 제공하는 다른 영역이 있습니다.
조기 최적화는 피해야한다는 의견에 전적으로 동의합니다.
그러나 Java VM에 switch ()에 사용할 수있는 특수 바이트 코드가 있다는 것은 사실입니다.
WM 사양 참조 ( lookupswitch 및 tableswitch )
따라서 코드가 성능 CPU 그래프의 일부인 경우 약간의 성능 향상이있을 수 있습니다.
if / else 또는 스위치가 성능 문제의 원인이 될 가능성은 매우 낮습니다. 성능 문제가있는 경우 먼저 성능 프로파일 링 분석을 수행하여 느린 지점이있는 위치를 확인해야합니다. 조기 최적화는 모든 악의 근원입니다!
그럼에도 불구하고 Java 컴파일러 최적화를 통해 스위치 대 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 (c i ) + 1-min (c i )) / 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));
}
}
switch
ES를 최적화하지 않았다고 확신 합니까?
Java 바이트 코드에는 두 가지 종류의 Switch 문이 있다는 것을 읽은 기억이 있습니다. ( 'Java Performance Tuning'에 있다고 생각합니다. One은 실행될 코드의 오프셋을 알기 위해 switch 문의 정수 값을 사용하는 매우 빠른 구현입니다.이를 위해서는 모든 정수가 연속적이며 잘 정의 된 범위에 있어야합니다. Enum의 모든 값을 사용하는 것도 해당 범주에 속할 것이라고 생각합니다.
나는 다른 많은 포스터들에 동의한다. 이것은 매우 핫한 코드가 아니라면 이것에 대해 걱정하는 것은 시기상조 일 수있다.
switch
여러 가지 방법을 구현 하며 일부는 다른 방법보다 효율적입니다. 일반적으로 효율성은 단순한 " if
사다리" 보다 나쁘지는 않지만 (특히 JITC의 경우) 그보다 훨씬 더 정확하기 어려운 충분한 변형이 있습니다.
클리프 클릭 (Cliff Click)의 2009 년 자바 원 토크 A Crash Course in Modern Hardware :
오늘날 성능은 메모리 액세스 패턴에 의해 좌우됩니다. 캐시 미스가 지배적입니다. 메모리가 새로운 디스크입니다. [슬라이드 65]
여기에서 그의 전체 슬라이드를 얻을 수 있습니다 .
Cliff는 CPU가 레지스터 이름 변경, 분기 예측 및 예측 실행을 수행하더라도 2 개의 캐시 미스로 인해 차단해야하기 전에 4 클럭주기에서 7 개의 작업 만 시작할 수 있음을 보여주는 예 (슬라이드 30에서 마무리)를 제공합니다. 반환 할 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
블록을 사용하는 경우 그 사용은 컴파일 타임에 알려진 상수 집합에서 가져온 값을 켜고 있음을 나타냅니다. 이 경우 상수 특정 메서드를 switch
사용할 수 있다면 문을 전혀 사용하지 않아야 enum
합니다.
switch
문에 비해 열거 형은 더 나은 형식 안전성과 유지 관리하기 쉬운 코드를 제공합니다. 상수 집합에 상수가 추가되면 새 값에 대한 상수 특정 메서드를 제공하지 않으면 코드가 컴파일되지 않도록 열거 형을 설계 할 수 있습니다. 반면 case
에, switch
블록에 새 항목을 추가하는 것을 잊은 것은 때때로 예외를 던지도록 블록을 설정할만큼 운이 좋은 경우에만 런타임에 포착 될 수 있습니다.
switch
와 enum
상수 특정 방법 간의 성능은 크게 다르지 않아야하지만 후자는 더 읽기 쉽고 안전하며 유지 관리하기 쉽습니다.