Java에서 if / else 대 switch 문의 상대적 성능 차이는 무엇입니까?


122

내 웹 응용 프로그램의 성능에 대해 걱정하면서 성능과 관련하여 "if / else"또는 switch 문 중 어느 것이 더 나은지 궁금합니다.


6
두 구문에 대해 동일한 바이트 코드가 생성되지 않는다고 생각할 이유가 있습니까?
Pascal Cuoq

2
@Pascal : 최적화의 목록 대신 테이블 룩업을 사용하여이 할 수있는 if
jldupont

18
"조기 최적화는 모든 악의 근원입니다"
-Donald

104
이것은 확실히 시기상조 인 최적화이지만, "오늘날 합리적으로 반응하는 GUI를 표시하기 위해 하이 엔드 멀티 코어 컴퓨터가 필요한 이유는 문맥에서 잘못 인용 된 인용문을 마음없이 고수하기 때문입니다."-Me.
Lawrence Dol

2
Knuth는 정확한 마음을 가지고 있습니다. 한정자 "미숙"에 유의하십시오. 최적화는 완벽하게 유효한 문제입니다. 즉, 서버는 IO 바운드이고 네트워크 및 디스크 I / O의 병목 현상은 서버에서 진행중인 다른 작업보다 훨씬 더 중요합니다.
alphazero 2011

답변:


108

그것은 사악한 마이크로 최적화와 조기 최적화입니다. 오히려 해당 코드의 가독성과 유지 관리 가능성에 대해 걱정하십시오. 두 개 이상의 if/else블록이 서로 붙어 있거나 크기를 예측할 수없는 경우 switch진술을 고려할 수 있습니다 .

또는 Polymorphism 을 가져올 수도 있습니다 . 먼저 인터페이스를 만듭니다.

public interface Action { 
    void execute(String input);
}

그리고 일부 Map. 이 작업은 정적으로 또는 동적으로 수행 할 수 있습니다.

Map<String, Action> actions = new HashMap<String, Action>();

마지막으로 if/elseor switch를 다음과 같이 대체합니다 (널 포인터와 같은 사소한 검사는 제쳐두고) :

actions.get(name).execute(input);

그것은 보다 microslower 일 if/else이나 switch,하지만 코드는 적어도 훨씬 더 유지 보수입니다.

웹 응용 프로그램에 대해 이야기 할 HttpServletRequest#getPathInfo()때 작업 키로 사용할 수 있습니다 (결국 작업이 발견 될 때까지 경로 정보의 마지막 부분을 루프에서 분리하는 코드를 더 작성). 여기에서 비슷한 답변을 찾을 수 있습니다.

일반적으로 Java EE 웹 애플리케이션 성능에 대해 걱정하고 있다면 이 기사도 유용 할 것입니다. 원시 Java 코드 만 (마이크로) 최적화하는 것보다 훨씬 더 많은 성능 향상 을 제공하는 다른 영역이 있습니다.


1
또는 대신 다형성을 고려하십시오
jk.

"예측할 수없는"양의 if / else 블록의 경우 실제로 더 권장됩니다.
BalusC

73
모든 초기 최적화를 "악"으로 무시하는 것은 그리 빠르지 않습니다. 너무 공격적이라는 것은 어리석은 짓이지만, 비교 가능한 가독성의 구조에 직면했을 때 더 잘 수행되는 것으로 알려진 것을 선택하는 것은 적절한 결정입니다.
Brian Knoblauch

8
HashMap 조회 버전은 tableswitsch 명령어에 비해 쉽게 10 배 더 느릴 수 있습니다. 나는 이것을 "마이크로 느리게"라고 부르지 않을 것입니다!
x4u

7
나는 스위치 문을 사용하는 일반적인 경우 Java의 내부 작동을 실제로 아는 데 관심이 있습니다. 누군가 이것이 초기 최적화 우선 순위를 초과하는 것과 관련이 있다고 생각하는지 여부에 관심이 없습니다. 즉, 나는이 답변이 왜 그렇게 많은 찬성 투표를 받았는지, 왜 그것이 받아 들여지는 대답인지 전혀 알지 못합니다 ... 이것은 초기 질문에 대한 대답이 아닙니다.
searchengine27

125

조기 최적화는 피해야한다는 의견에 전적으로 동의합니다.

그러나 Java VM에 switch ()에 사용할 수있는 특수 바이트 코드가 있다는 것은 사실입니다.

WM 사양 참조 ( lookupswitchtableswitch )

따라서 코드가 성능 CPU 그래프의 일부인 경우 약간의 성능 향상이있을 수 있습니다.


60
이 댓글이 더 높은 등급이 아닌 이유가 궁금합니다.이 댓글이 가장 유익한 정보입니다. 내 말은 : 우리 모두는 이미 조기 최적화가 나쁘다는 것을 알고 있기 때문에 1000 번째로 설명 할 필요가 없습니다.
Folkert van Heusden

5
+1 stackoverflow.com/a/15621602/89818 현재 성능 향상이 실제로 존재하는 것으로 보이며 18+ 케이스를 사용하면 이점을 볼 수 있습니다.
caw

52

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 문은 완전히 쓸모가 없습니다.


+1. 네트워크 I / O에 소요 된 시간이이 특정 문제를 쉽게 가릴 가능성이 있습니다.
Adam Paynter

3
스위치는 int 이상으로 작동한다는 점에 유의해야합니다. Java Tutorials에서 : "스위치는 byte, short, char 및 int 기본 데이터 유형과 함께 작동합니다. 또한 열거 유형 (Enum 유형에서 논의 됨), String 클래스 및 특정 기본 유형을 래핑하는 몇 가지 특수 클래스와도 작동합니다. : Character, Byte, Short 및 Integer (숫자 및 문자열에서 설명). " String 지원은 최근에 추가되었습니다. Java 7에 추가되었습니다. docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
atraudes 2014 년

1
@jhonFeminella Swtich의 Java7 String에 대한 BIG O 개념 효과를 if / else if ..의 String과 비교해 주시겠습니까?
Kanagavelu Sugumar 2014

더 정확하게는 javac 8은 공간 복잡성에 대한 시간 복잡성에 3의 가중치를 부여합니다. stackoverflow.com/a/31032054/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

11

스위치를 사용하십시오!

나는 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));
    }
}

벤치마킹을위한 내 C # 표준 코드


이것을 벤치마킹 한 방법에 대해 (때때로) 조금 자세히 설명해 주시겠습니까?
DerMike 2014

업데이트 해주셔서 감사합니다. 내 말은, 그것들은 한 자릿수 차이가 있습니다. 물론 가능합니다. 컴파일러가 switchES를 최적화하지 않았다고 확신 합니까?
DerMike 2014

@DerMike 나는 내가 어떻게 오래된 결과를 얻었는지 기억이 나지 않습니다. 오늘은 매우 달라졌습니다. 그러나 직접 시도해보고 결과를 알려주십시오.
Bitterblue 2014

1
내 노트북에서 실행할 때; 필요한 전환 시간 : 3585, if / else 필요한 시간 : 3458 그래서 if / else가 더 낫거나 나쁘지 않습니다
halil

1
테스트의 주요 비용은 난수 생성입니다. 루프 전에 난수를 생성하도록 테스트를 수정하고 임시 값을 사용하여 r에 피드백했습니다. 그러면 스위치는 if-else 체인보다 거의 두 배 빠릅니다.
boneill

8

Java 바이트 코드에는 두 가지 종류의 Switch 문이 있다는 것을 읽은 기억이 있습니다. ( 'Java Performance Tuning'에 있다고 생각합니다. One은 실행될 코드의 오프셋을 알기 위해 switch 문의 정수 값을 사용하는 매우 빠른 구현입니다.이를 위해서는 모든 정수가 연속적이며 잘 정의 된 범위에 있어야합니다. Enum의 모든 값을 사용하는 것도 해당 범주에 속할 것이라고 생각합니다.

나는 다른 많은 포스터들에 동의한다. 이것은 매우 핫한 코드가 아니라면 이것에 대해 걱정하는 것은 시기상조 일 수있다.


4
핫 코드 댓글에 +1. 메인 루프에있는 경우 조기가 아닙니다.
KingAndrew 2014

예, javacswitch여러 가지 방법을 구현 하며 일부는 다른 방법보다 효율적입니다. 일반적으로 효율성은 단순한 " if사다리" 보다 나쁘지는 않지만 (특히 JITC의 경우) 그보다 훨씬 더 정확하기 어려운 충분한 변형이 있습니다.
Hot Licks 2014

8

클리프 클릭 (Cliff Click)의 2009 년 자바 원 토크 A Crash Course in Modern Hardware :

오늘날 성능은 메모리 액세스 패턴에 의해 좌우됩니다. 캐시 미스가 지배적입니다. 메모리가 새로운 디스크입니다. [슬라이드 65]

여기에서 그의 전체 슬라이드를 얻을 수 있습니다 .

Cliff는 CPU가 레지스터 이름 변경, 분기 예측 및 예측 실행을 수행하더라도 2 개의 캐시 미스로 인해 차단해야하기 전에 4 클럭주기에서 7 개의 작업 만 시작할 수 있음을 보여주는 예 (슬라이드 30에서 마무리)를 제공합니다. 반환 할 300 클록 사이클.

그래서 그는 프로그램 속도를 높이기 위해 이런 종류의 사소한 문제를 살펴서는 안되며 "SOAP → XML → DOM → SQL →… "캐시를 통해 모든 데이터를 전달합니다."


4

내 테스트에서 더 나은 성능은 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
halil

@halil이 코드가 다른 환경에서 어떻게 작동하는지 잘 모르겠지만 if / elseif가 Switch 및 Map보다 낫다고 언급했습니다.
Kanagavelu Sugumar 2015 년

2

대부분 switch가장 if-then-else블록, 나는 어떤 감지 또는 상당한 성능 관련 문제가 있다는 것을 상상할 수 없다.

그러나 여기에 문제가 있습니다. switch블록을 사용하는 경우 그 사용은 컴파일 타임에 알려진 상수 집합에서 가져온 값을 켜고 있음을 나타냅니다. 이 경우 상수 특정 메서드를 switch사용할 수 있다면 문을 전혀 사용하지 않아야 enum합니다.

switch문에 비해 열거 형은 더 나은 형식 안전성과 유지 관리하기 쉬운 코드를 제공합니다. 상수 집합에 상수가 추가되면 새 값에 대한 상수 특정 메서드를 제공하지 않으면 코드가 컴파일되지 않도록 열거 형을 설계 할 수 있습니다. 반면 case에, switch블록에 새 항목을 추가하는 것을 잊은 것은 때때로 예외를 던지도록 블록을 설정할만큼 운이 좋은 경우에만 런타임에 포착 될 수 있습니다.

switchenum상수 특정 방법 간의 성능은 크게 다르지 않아야하지만 후자는 더 읽기 쉽고 안전하며 유지 관리하기 쉽습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.