거대한“스위치”설명 대신 OO 접근 방식을 사용해야하는 이유는 무엇입니까?


59

저는 .Net, C # 상점에서 일하고 있으며 더 많은 객체 지향 접근 방식이 아닌 많은 "케이스"와 함께 코드에 거대한 Switch 문을 사용해야한다고 주장하는 동료가 있습니다. 그의 주장은 Switch 문이 "cpu jump table"로 컴파일되고 따라서 가장 빠른 옵션이라는 사실로 일관되게 되돌아갑니다.

나는 솔직히 이것에 대해 논쟁을하지 않습니다 ... 왜냐하면 그가 무슨 말을하는지 알지 못하기 때문입니다.
그가 맞습니까?
방금 엉덩이를 말하는거야?
여기서 배우려고합니다.


7
.NET Reflector와 같은 것을 사용하여 어셈블리 코드를보고 "cpu jump table"을 찾아서 올바른지 확인할 수 있습니다.
FrustratedWithFormsDesigner

5
"Switch 문은"cpu jump table "로 컴파일됩니다. 따라서 모든 순수 가상 함수로 최악의 메서드 디스패치가 수행됩니다. 가상 함수는 직접 링크되지 않습니다. 비교할 코드를 덤프 했습니까?
S.Lott

64
기계가 아닌 PEOPLE을위한 코드를 작성해야합니다. 그렇지 않으면 어셈블리의 모든 작업을 수행합니다.
maple_shaft

8
Knoth가 그렇게 많은 평가를 받고 있다면, Knuth는 다음과 같이 인용한다.
DaveE

12
유지 보수성. 하나의 단어로 답변 할 수있는 다른 질문이 있습니까?
매트 엘렌

답변:


48

그는 아마도 오래된 C 해커 일 것입니다. 그렇습니다. .Net은 C ++이 아닙니다. .Net 컴파일러는 계속해서 나아지고 있으며 오늘날 대부분의 .Net 버전에서는 대부분의 영리한 핵이 역효과를냅니다. .Net JIT는 사용하기 전에 모든 기능을 한 번만 사용하므로 작은 기능이 바람직합니다. 따라서 프로그램의 LifeCycle 중에 일부 사례가 발생하지 않으면 JIT 컴파일시 비용이 발생하지 않습니다. 어쨌든 속도가 문제가되지 않으면 최적화가 없어야합니다. 프로그래머를 위해, 컴파일러를 위해 두 번째로 작성하십시오. 동료는 쉽게 확신 할 수 없으므로 체계적으로 코드를 작성하면 코드가 더 빠르다는 것을 경험적으로 증명할 수 있습니다. 그의 최악의 예 중 하나를 골라 더 나은 방법으로 다시 작성한 다음 코드가 더 빠르도록하십시오. 필요한 경우 체리 픽. 그런 다음 몇 백만 번 실행하고 프로파일 링하고 보여주십시오.

편집하다

빌 바그너는 다음과 같이 썼습니다.

항목 11 : 작은 함수의 매력 이해 (효과적인 C # Second Edition) C # 코드를 컴퓨터 실행 가능 코드로 변환하는 것은 2 단계 프로세스입니다. C # 컴파일러는 어셈블리로 제공되는 IL을 생성합니다. JIT 컴파일러는 필요에 따라 각 메소드 (또는 인라인이 관련된 메소드 그룹)에 대한 머신 코드를 생성합니다. 작은 함수를 사용하면 JIT 컴파일러가 해당 비용을 쉽게 상각 할 수 있습니다. 작은 함수도 인라인 후보가 될 가능성이 높습니다. 단지 작은 것이 아닙니다. 간단한 제어 흐름이 중요합니다. 함수 내부의 제어 분기가 줄어들면 JIT 컴파일러가 변수를 쉽게 등록 할 수 있습니다. 더 명확한 코드를 작성하는 것은 좋은 습관이 아닙니다. 런타임에보다 효율적인 코드를 만드는 방법입니다.

EDIT2 :

따라서 ... 한 비교는 대수이고 다른 비교는 선형이기 때문에 switch 문이 if / else 문보다 빠르고 빠릅니다. http://sequence-points.blogspot.com/2007/10/why-is-switch-statement-faster-than-if.html

글쎄, 거대한 switch 문을 대체하는 가장 좋아하는 접근법은 사전 (또는 때로는 열거 형이나 작은 정수를 전환하는 경우 배열)을 사용하여 값을 응답으로 호출되는 함수에 매핑하는 것입니다. 그렇게하면 많은 불쾌한 공유 스파게티 상태를 제거해야하지만 좋은 일입니다. 큰 스위치 설명은 일반적으로 유지 관리의 악몽입니다. 따라서 ... 배열과 사전을 사용하면 조회에 일정한 시간이 걸리고 여분의 메모리 낭비가 거의 없습니다.

나는 여전히 switch 문이 더 낫다는 것을 확신하지 못한다.


47
더 빨리 증명하는 것에 대해 걱정하지 마십시오. 이것은 조기 최적화입니다. 저장할 수있는 밀리 초는 200ms의 비용이 드는 데이터베이스에 추가하는 것을 잊어 버린 인덱스와 비교할 수 없습니다. 당신은 잘못된 전투와 싸우고 있습니다.
Rein Henrichs

27
@Job 그가 실제로 옳다면? 요점은 그가 틀렸다는 것이 아니라 요점은 그가 옳고 중요하지 않다는 것 입니다.
Rein Henrichs

2
그가 사건의 약 100 %가 옳았더라도 그는 여전히 우리 시간을 낭비하고 있습니다.
Jeremy

6
나는 당신이 링크 한 페이지를 읽으려고 노력하고 있습니다.
AttackingHobo

3
C ++는 무엇입니까? C ++ 컴파일러도 향상되고 있으며 큰 스위치는 C #에서와 마찬가지로 C ++에서와 똑같은 이유로 나쁩니다. 슬프게하는 전 C ++ 프로그래머들에게 둘러싸여 있다면 C ++ 프로그래머이기 때문이 아니라 나쁜 프로그래머이기 때문입니다.
Sebastian Redl

39

동료가 증거를 제공 할 수 없다면이 변경이 전체 응용 프로그램의 규모에 대해 실질적으로 측정 가능한 이점을 제공한다는 점 에서 접근 방식 (예 : 다형성)보다 열등하므로 실제로 이러한 이점을 제공합니다.

병목 현상이 해결 된 후에 만 미세 최적화를 수행해야 합니다. 조기 최적화는 모든 악의 근원입니다 .

속도는 정량화 할 수 있습니다. "접근 A가 접근 B보다 빠릅니다"에는 유용한 정보가 거의 없습니다. 문제는 " 얼마나 빠릅니까? "입니다.


2
물론 그렇습니다. 무언가가 더 빠르다고 주장 하지 말고 항상 측정하십시오. 응용 프로그램의 해당 부분이 성능 병목 현상 인 경우에만 측정하십시오.
Kilian Foth

6
"조기 최적화는 모든 악의 근원"입니다. 크 누스의 의견을 편향시키는 부분 만이 아니라 전체 인용문을 표시하십시오 .
대안

2
@mathepic : 의도적으로 이것을 인용으로 제시하지 않았습니다. 물론이 문장은 내 개인의 의견이지만 물론 내 창조물은 아니다. c2의 사람들은 그 부분을 핵심 지혜라고 생각하는 것 같습니다.
back2dos

8
@alternative 전체 Knuth 인용문 "효율의 성배는 남용으로 이어질 것이라는 데는 의심의 여지가 없습니다. 프로그래머들은 프로그램의 중요하지 않은 부분의 속도에 대해 생각하거나 걱정하는 데 많은 시간을 낭비하며, 효율성을위한 이러한 시도에는 실제로 디버깅과 유지 보수를 고려할 때 강력한 부정적인 영향을 미칩니다. 우리는 시간의 약 97 %라는 작은 효율성을 잊어야합니다. 조기 최적화는 모든 악의 근원입니다. " OP의 동료를 완벽하게 설명합니다. IMHO의 back2dos는 "조기 최적화는 모든 악의 근원"이라고 인용을 잘 요약했습니다
MarkJ

2
@MarkJ 시간의 97 %
대안

27

누가 더 빠른지 누가 신경 쓰나요?

실시간 소프트웨어를 작성하지 않는 한 완전히 미친 방식으로 무언가를 수행함으로써 얻을 수있는 약간의 속도 향상이 고객에게 큰 영향을 줄 것 같지 않습니다. 나는 이것의 속도를 앞당기 지 않을 것입니다.이 녀석은 분명히 주제에 대한 논쟁을 듣지 않을 것입니다.

그러나 유지 관리는 게임의 목표이며, 거대한 스위치 설명은 약간 유지 관리가 불가능합니다. 코드를 통해 새로운 사람들에게 이르는 다른 경로를 어떻게 설명합니까? 문서는 코드 자체만큼 길어야합니다!

또한, 단위 테스트를 효과적으로 수행 할 수 없어 (인터페이스 부족 등은 말할 것도없고 너무 많은 경로) 코드를 유지 관리하기가 훨씬 어려워졌습니다.

[관심있는 측면에서 : JITter는 더 작은 방법에서 더 나은 성능을 발휘하므로 대형 스위치 문 (및 본질적으로 큰 방법)은 대형 조립품 (IIRC)에서 속도를 떨어 뜨립니다.]


1
+ 조기 최적화의 거대한 예.
ShaneC

확실히 이것입니다.
DeadMG

'거대한 스위치 설명은 약간 유지 보수가 불가능하다'+1
Korey Hinton

2
새로운 스위치 사용자가 이해하기 쉬운 거대한 스위치 선언문이 있습니다. 가능한 모든 동작이 깔끔한 목록으로 바로 수집됩니다. 간접 호출은 따라 가기가 매우 어렵습니다. 최악의 경우 (함수 포인터) 올바른 서명의 기능에 대해 전체 코드베이스를 검색해야하며 가상 호출은 조금 더 좋습니다 (올바른 이름 및 서명의 기능 검색 및 상속 관련). 그러나 유지 관리 성은 읽기 전용이 아닙니다.
Ben Voigt

14

switch 문에서 멀어 지십시오 ...

이러한 유형의 switch 문은 Open Closed Principle을 위반하기 때문에 전염병처럼 피해야합니다 . 새로운 코드를 추가하는 대신 새로운 기능을 추가해야 할 때 팀이 기존 코드를 변경하도록합니다.


11
주의 사항이 있습니다. 작업 (기능 / 방법) 및 유형이 있습니다. 새 작업을 추가 할 때 switch 문에 대해 한 곳에서 코드를 변경하면되고 (switch 문으로 새 함수 하나 추가) OO 경우 모든 클래스에 해당 메서드를 추가해야합니다 (열기 위반) / 닫힌 원칙). 새 유형을 추가하는 경우 모든 switch 문을 터치해야하지만 OO 경우에는 클래스를 하나 더 추가하면됩니다. 따라서 현명한 결정을 내리려면 기존 유형에 더 많은 작업을 추가 할 것인지 또는 더 많은 유형을 추가 할 것인지 알아야합니다.
Scott Whitlock

3
OCP를 위반하지 않고 OO 패러다임의 기존 유형에 더 많은 작업을 추가해야하는 경우 방문자 패턴이 적합하다고 생각합니다.
Scott Whitlock

3
@Martin-당신이 원한다면 이름을 부르십시오, 그러나 이것은 잘 알려진 트레이드 오프입니다. RC Martin의 Clean Code를 참조하십시오. 그는 위에서 설명한 내용을 설명하면서 OCP에 대한 기사를 다시 살펴 봅니다. 미래의 모든 요구 사항에 대해 동시에 디자인 할 수는 없습니다. 더 많은 작업을 추가 할 것인지 더 많은 유형을 추가 할 것인지를 선택해야합니다. OO는 유형 추가를 선호합니다. 오퍼레이션을 클래스로 모델링하는 경우 OO를 사용하여 더 많은 오퍼레이션을 추가 할 수 있지만 자체 패턴 (특히 오버 헤드)이있는 방문자 패턴에 도달하는 것 같습니다.
Scott Whitlock

8
@ 마틴 : 파서를 쓴 적이 있습니까? lookahead 버퍼에서 다음 토큰을 켜는 큰 스위치 케이스를 갖는 것이 일반적입니다. 이러한 스위치를 다음 토큰에 대한 가상 함수 호출로 교체 할 수 있지만 유지 관리의 악몽이 될 것입니다. 드문 일이지만 때로는 스위치 케이스가 실제로 더 나은 선택입니다. 코드를 서로 가깝게 읽고 수정해야하기 때문입니다.
nikie

1
@Martin : "never", "ever"및 "Poppycock"과 같은 단어를 사용했기 때문에 가장 일반적인 경우가 아니라 예외없이 모든 사례에 대해 이야기하고 있다고 가정했습니다. (그리고 BTW : 사람들은 여전히 ​​손으로 파서를 작성합니다. 예를 들어 CPython 파서는 여전히 손으로 작성됩니다, IIRC)
nikie

8

나는 거대한 스위치 문으로 조작되는 거대한 유한 상태 기계로 알려진 악몽에서 살아 남았습니다. 더 나쁜 것은 제 경우에는 FSM이 3 개의 C ++ DLL에 걸렸으며 C에 정통한 누군가가 코드를 작성하는 것이 매우 분명했습니다.

주의해야 할 측정 항목은 다음과 같습니다.

  • 변화 속도
  • 문제가 발생했을 때 발견하는 속도

나는 그 DLL 세트에 새로운 기능을 추가하는 작업을 받았으며, 원숭이 패치에 대한 것처럼 하나의 적절한 객체 지향 DLL만큼 3 개의 DLL을 다시 작성하는 데 시간이 걸릴 것이라고 경영진에게 확신시킬 수있었습니다. 배심원은 이미 존재했던 것에 솔루션을 조작합니다 새로운 기능을 지원했을뿐만 아니라 확장하기가 훨씬 쉬우므로 재 작성은 큰 성공을 거두었습니다. 실제로, 일주일이 걸리면 아무것도 끊지 않도록하는 데 몇 시간이 걸릴 수 있습니다.

실행 시간은 어떻습니까? 속도 증가 또는 감소는 없었습니다. 공정하게 말하면 시스템 드라이버가 성능을 조절했기 때문에 객체 지향 솔루션이 실제로 느린 경우에는 알 수 없습니다.

OO 언어에 대한 대규모 스위치 문에 어떤 문제가 있습니까?

  • 프로그램 제어 흐름은 개체가 속한 개체에서 제거되어 개체 외부에 배치됩니다.
  • 많은 외부 제어 지점을 검토해야하는 많은 장소로 변환
  • 스위치가 루프 내부에있는 경우 상태가 저장된 위치가 확실하지 않습니다.
  • 가장 빠른 비교는 전혀 비교가 필요하지 않습니다 (좋은 객체 지향 디자인으로 많은 비교가 필요하지 않음)
  • 객체를 반복하는 것이 더 효율적이며 항상 객체 유형 또는 유형을 인코딩하는 열거 형을 기반으로 코드를 변경하는 것보다 모든 객체에서 동일한 메소드를 호출하는 것이 좋습니다.

8

나는 성능 주장을 사지 않는다. 그것은 코드 유지 관리에 관한 것입니다.

그러나 때로는 거대한 기본 스위치의 가상 기능을 재정의하는 여러 개의 작은 클래스보다 거대한 switch 문을 유지 관리하기가 더 쉽습니다 (코드가 적음). 예를 들어, CPU 에뮬레이터를 구현하려는 경우 각 명령의 기능을 별도의 클래스로 구현하지 않을 것입니다. opcode의 거대한 swtich에 넣어서 복잡한 명령을위한 도우미 함수를 호출 할 수 있습니다.

경험적 규칙 : 스위치가 TYPE에서 어떻게 든 수행되는 경우 상속 및 가상 기능을 사용해야합니다. 고정 유형의 VALUE (예 : 위의 명령어 opcode)에서 스위치를 수행하는 경우 그대로 두는 것이 좋습니다.


5

당신은 저를 설득 할 수 없습니다 :

void action1()
{}

void action2()
{}

void action3()
{}

void action4()
{}

void doAction(int action)
{
    switch(action)
    {
        case 1: action1();break;
        case 2: action2();break;
        case 3: action3();break;
        case 4: action4();break;
    }
}

다음보다 훨씬 빠릅니다.

struct IAction
{
    virtual ~IAction() {}
    virtual void action() = 0;
}

struct Action1: public IAction
{
    virtual void action()    { }
}

struct Action2: public IAction
{
    virtual void action()    { }
}

struct Action3: public IAction
{
    virtual void action()    { }
}

struct Action4: public IAction
{
    virtual void action()    { }
}

void doAction(IAction& actionObject)
{
    actionObject.action();
}

또한 OO 버전은 유지 관리가 더 쉽습니다.


8
일부 사항과 적은 양의 작업의 경우 OO 버전이 훨씬 더 쉽습니다. 가치를 IAction의 창출로 전환하려면 일종의 공장이 있어야합니다. 많은 경우에 그 값을 대신 켜는 것이 훨씬 더 읽기 쉽습니다.
Zan Lynx

@ 잔 Lynx : 당신의 주장은 너무 일반적입니다. IAction 오브젝트 작성은 조치 정수를 더 이상 쉽게 검색하는 것만 큼 어렵습니다. 따라서 일반적인 방법으로 대화 할 수 없습니다. 계산기를 고려하십시오. 여기서 복잡성의 차이점은 무엇입니까? 답은 0입니다. 모든 행동이 미리 만들어진 것처럼. 사용자로부터 입력을 받고 이미 조치를 취했습니다.
Martin York

3
@Martin : GUI 계산기 앱을 가정하고 있습니다. 대신 임베디드 시스템에서 C ++ 용으로 작성된 키보드 계산기 앱을 보자. 이제 하드웨어 레지스터의 스캔 코드 정수가 있습니다. 이제 덜 복잡한 것은 무엇입니까?
Zan Lynx

2
@ 마틴 : 정수-> 조회 테이블-> 새 객체 생성-> 가상 함수 호출이 정수-> 스위치-> 함수보다 얼마나 복잡한 지 보지 못합니까? 어떻게 보지 않습니까?
Zan Lynx 16:26에

2
@Martin : 어쩌면 내가 할 것이다. 그 동안 룩업 테이블이 없는 정수에서 IAction 객체가 action ()을 호출하는 방법을 설명 하십시오.
Zan Lynx

4

그는 결과 머신 코드가 더 효율적일 것 입니다 . 컴파일러는 필수적으로 switch 문을 일련의 테스트 및 분기로 변환하는데, 이는 명령이 상대적으로 적습니다. 보다 추상적 인 접근 방식으로 인한 코드에 더 많은 명령이 필요할 가능성이 높습니다.

그러나 특정 응용 프로그램이 이러한 종류의 마이크로 최적화에 대해 걱정할 필요가 없거나 .net을 처음 사용하지 않을 것입니다. 매우 제한된 임베디드 응용 프로그램 또는 CPU 집약적 작업이 부족한 경우 항상 컴파일러가 최적화를 처리하도록해야합니다. 깨끗하고 유지 관리 가능한 코드 작성에 집중하십시오. 이것은 거의 항상 실행 시간에서 수십 분의 1 나노초보다 훨씬 큰 가치가 있습니다.


3

switch 문 대신 클래스를 사용하는 주된 이유 중 하나는 switch 문이 많은 논리를 가진 하나의 거대한 파일로 이어지기 때문입니다. 다른 작은 클래스 파일 대신 큰 파일을 체크 아웃하고 편집해야하기 때문에 이것은 유지 관리의 악몽 일뿐 아니라 소스 관리의 문제이기도합니다.


3

OOP 코드의 switch 문은 누락 된 클래스를 강력하게 표시합니다.

두 가지 방법 모두 시도하고 간단한 속도 테스트를 실행하십시오. 차이가 크지 않을 가능성이 있습니다. 이러한 경우 와 코드가 시간이 중요 하고 스위치 문을 계속


3

일반적으로 나는 "조기 최적화"라는 단어를 싫어하지만,이 문제는 심각합니다. Knuth goto중요한 영역의 코드 속도를 높이기 위해 명령문 을 사용하도록 추진하는 맥락 에서이 유명한 인용문을 사용했다는 점은 주목할 가치가 있습니다. 이것이 핵심입니다. 중요한 길.

그는 goto코드 속도를 높이는 데 사용할 것을 제안 했지만 중요하지 않은 코드에 대한 미신과 미신을 기반으로 이러한 유형의 작업을 수행하려는 프로그래머에게는 경고했습니다.

Knuth가 "최적화 및 파운드 어리 석음"프로그래머라고 부르는 전형적인 예는 코드베이스 전체에서 switch가능한 한 균일하게 진술 을 선호 하는 것입니다. "파운드 이상으로 동전을 절약하려고 시도한 결과 디버깅 악몽으로 바뀌는 코드입니다. 이러한 코드는 처음부터 효율적일뿐 아니라 유지 보수가 거의 불가능합니다.

그가 맞습니까?

그는 매우 기본적인 효율성 관점에서 옳습니다. 내가 아는 컴파일러는 스위치 문보다 객체 및 동적 디스패치와 관련된 다형성 코드를 최적화 할 수 없습니다. 다형성 코드에서 인라인 코드로의 LUT 또는 점프 테이블로 끝나지 않을 것입니다. 이러한 코드는 컴파일러의 최적화 장벽으로 사용되는 경향이 있기 때문에 (동적 디스패치 시점까지 어떤 함수를 호출해야하는지 알 수 없습니다) 발생).

이 비용을 점프 테이블의 관점에서 생각하지 않고 최적화 장벽의 관점에서 더 많이 생각하는 것이 더 유용합니다. 다형성의 경우 호출을 Base.method()사용하면 컴파일러 method가 가상이 아니고 봉인되지 않고 재정의 될 수 있는 함수가 실제로 호출되는 것을 알 수 없습니다. 실제로 어떤 함수가 미리 호출되는지 알지 못하므로 함수 호출을 최적화하고 최적화 결정을 내리는 데 더 많은 정보를 활용할 수는 없습니다. 실제로 어떤 함수가 호출 될지 알 수 없기 때문입니다. 코드가 컴파일되는 시간

옵티마이 저는 함수 호출에 피어링하고 발신자와 수신자를 완전히 평평하게하거나 최소한 수신자를 최적화하여 수신자와 가장 효율적으로 작업하도록 최적화 할 수 있습니다. 어떤 함수가 실제로 미리 호출 될지 모른다면 그렇게 할 수 없습니다.

방금 엉덩이를 말하는거야?

일반적으로 페니에 해당하는이 비용을 사용하여이를 균일하게 적용되는 코딩 표준으로 바꾸는 것을 정당화하는 것은 일반적으로 매우 어리석은 일입니다. 특히 확장 성이 필요한 곳에서는 더욱 그렇습니다. 이것이 진정한 조기 옵티 마이저에서주의해야 할 주요 사항입니다. 유지 보수성에 관계없이 사소한 성능 문제를 코드베이스 전체에 균일하게 적용되는 코딩 표준으로 바꾸려고합니다.

그래도 그 중 하나이기 때문에 받아 들여진 대답에 사용 된 "오래된 C 해커"인용문에 약간의 위반이 있습니다. 매우 제한된 하드웨어에서 시작하여 수십 년 동안 코딩을해온 사람이 모두 조기 최적화 프로그램이 된 것은 아닙니다. 그러나 나는 그들과도 만나서 함께 일했습니다. 그러나 이러한 유형은 분기 오판 또는 캐시 미스와 같은 것을 측정하지 않으며, 그들이 더 잘 알고 있다고 생각하며 오늘날에는 적용되지 않는 미신에 기반한 복잡한 프로덕션 코드베이스에 비 효율성이라는 개념을 기반으로하며 때로는 결코 참지 않습니다. 성능이 중요한 분야에서 진정으로 일한 사람들은 효과적인 최적화가 효과적인 우선 순위 지정이라는 사실을 잘 알고 있으며, 페니를 절약하기 위해 유지 보수성 저하 코딩 표준을 일반화하는 것은 매우 비효율적 인 우선 순위 지정입니다.

페니는 매우 빡빡하고 성능이 중요한 루프에서 10 억 번 호출되는 일을 많이하지 않는 저렴한 기능을 사용할 때 중요합니다. 이 경우 1,000 만 달러를 절약하게됩니다. 몸만으로도 수천 달러의 비용이 드는 기능을 두 번 호출하면 페니를 면도 할 가치가 없습니다. 자동차를 구매하는 동안 시간을 ​​낭비하는 것이 현명하지 않습니다. 제조업체에서 백만 캔의 음료수를 구입하는 경우 페니를 넘겨 볼 가치가 있습니다. 효과적인 최적화의 핵심은 적절한 비용으로 이러한 비용을 이해하는 것입니다. 구매할 때마다 돈을 절약하려고 노력하는 사람은 다른 사람이 구매하는 제품에 관계없이 돈을 벌려고 시도하는 것이 숙련 된 최적화 프로그램이 아니라고 제안합니다.


2

동료가 성능에 매우 관심이있는 것 같습니다. 경우에 따라 큰 케이스 / 스위치 구조가 더 빠르게 수행 될 수 있지만 OO 버전 및 스위치 / 케이스 버전에서 타이밍 테스트를 수행하여 실험을 수행하길 바랍니다. OO 버전은 코드가 적고 따르고 이해하고 유지하기가 더 쉽다고 생각합니다. OO 버전을 먼저 주장하고 (유지 보수 / 가독성이 처음에 더 중요해야 함) OO 버전에 심각한 성능 문제가 있고 스위치 / 케이스가 상당한 개선.


1
타이밍 테스트와 함께 코드 덤프는 C ++ (및 C #) 메서드 디스패치 작동 방식을 보여줍니다.
S.Lott

2

아무도 언급하지 않은 다형성의 유지 관리 이점 중 하나는 항상 동일한 사례 목록을 전환하는 경우 상속을 사용하여 코드를 훨씬 더 잘 구성 할 수 있지만 때로는 여러 사례가 동일한 방식으로 처리되는 경우가 있습니다. 그렇지 않다

예 : 당신이 사이를 전환하는 경우 Dog, Cat그리고 Elephant, 때로는 DogCat같은 경우가, 당신은 그들 모두가 추상 클래스에서 상속 할 수 있습니다 DomesticAnimal와 추상 클래스에서 그 기능을 넣어.

또한 여러 사람들이 다형성을 사용하지 않는 곳의 예로 파서를 사용했다는 사실에 놀랐습니다. 나무와 같은 파서의 경우 이것은 분명히 잘못된 접근법이지만 각 줄이 다소 독립적 인 어셈블리와 같은 것이 있고 나머지 줄을 해석하는 방법을 나타내는 opcode로 시작하면 전적으로 다형성을 사용합니다 그리고 공장. 각 클래스는 같은 기능을 구현할 수 있습니다 ExtractConstants또는 ExtractSymbols. 나는이 접근법을 장난감 기본 통역사에 사용했습니다.


스위치는 기본 경우를 통해 동작을 상속 할 수 있습니다. "... BaseOperationVisitor를 확장합니다"가 "기본값 : BaseOperation (노드)"가됩니다
Samuel Danielson

0

"우리는 시간의 97 % 정도의 작은 효율성을 잊어야합니다. 조기 최적화는 모든 악의 근원입니다"

도널드 크 누스


0

이것이 유지 관리가 나쁘지 않더라도 성능이 더 좋을 것이라고는 생각하지 않습니다. 가상 함수 호출은 단순히 하나의 추가 간접 지시 (switch 문의 가장 좋은 경우와 동일)이므로 C ++에서도 성능은 대략 동일해야합니다. 모든 함수 호출이 가상 인 C #에서는 두 버전에서 동일한 가상 함수 호출 오버 헤드가 있으므로 switch 문이 더 나빠 야합니다.


1
"없음"이 없습니까? C #에서 모든 함수 호출이 가상 인 것은 아닙니다 . C #은 Java가 아닙니다.
Ben Voigt

0

점프 테이블에 대한 의견이있는 한 동료가 뒷면에서 이야기하지 않습니다. 그러나 나쁜 코드 작성을 정당화하기 위해 그것을 사용하는 것은 그가 잘못한 곳입니다.

C # 컴파일러는 몇 가지 경우 만있는 switch 문을 일련의 if / else로 변환하므로 if / else를 사용하는 것보다 빠르지 않습니다. 컴파일러는 더 큰 switch 문을 Dictionary (동료가 참조하는 점프 테이블)로 변환합니다. 자세한 내용은이 주제에 대한 스택 오버플로 질문에 대한 답변을 참조하십시오 .

큰 switch 문은 읽고 유지 관리하기가 어렵습니다. "cases"와 함수의 사전은 훨씬 쉽게 읽을 수 있습니다. 이것이 스위치가되는 것이므로, 당신과 동료는 사전을 직접 사용하는 것이 좋습니다.


0

그는 반드시 그의 엉덩이에서 말하는 것은 아닙니다. 적어도 C 및 C ++ switch문에서는 테이블을 점프하도록 최적화 할 수 있지만 기본 포인터에만 액세스 할 수있는 함수에서 동적 디스패치로 발생하는 것을 본 적이 없습니다. 최소한 후자는 기본 포인터 / 참조를 통해 가상 함수 호출에서 어떤 하위 유형이 사용되고 있는지 정확히 파악하기 위해 훨씬 더 주변 코드를보고 훨씬 더 똑똑한 최적화 프로그램이 필요합니다.

게다가 동적 디스패치는 종종 "최적화 장벽"역할을합니다. 즉, 컴파일러는 코드 유출을 최소화하기 위해 코드를 인라인하고 레지스터를 최적으로 할당 할 수 없으며, 스택 유출과 모든 멋진 것들을 최소화 할 수 없습니다. 가상 함수는 기본 포인터를 통해 호출되어 인라인하고 모든 최적화 마법을 수행합니다. 나는 당신이 옵티마이 저가 너무 똑똑하고 간접적 인 함수 호출을 최적화하려고한다고 확신하지 못한다. 왜냐하면 주어진 호출 스택 (호출 foo->f()이 가진 함수) 아래에서 많은 코드 분기가 별도로 생성되어야 할 수 있기 때문에 호출하는 것과 완전히 다른 머신 코드를 생성bar->f() 기본 포인터를 통해 해당 함수를 호출하는 함수는 두 가지 이상의 코드 버전을 생성해야합니다. 생성되는 머신 코드의 양은 폭발적 일 것입니다. 핫 실행 경로를 추적하면서 즉시 코드를 생성합니다).

그러나 많은 답변이 반향 되었기 때문에, switch약간의 여유가 있더라도 배를 싣는 것이 바람직합니다. 또한 미시적 효율성과 관련하여 분기 및 인라인과 같은 것은 메모리 액세스 패턴과 같은 것에 비해 일반적으로 우선 순위가 매우 낮습니다.

즉, 나는 이상한 대답으로 여기에 뛰어 들었습니다. 나는 switch다형성 솔루션에 대한 진술 의 유지 관리 가능성에 대해 사례를 만들고 싶습니다 switch.

주요 예는 중앙 이벤트 핸들러입니다. 이 경우 일반적으로 이벤트를 처리하는 장소가 많지 않고 하나만 있습니다 ( "중앙"이유). 이러한 경우 다형성 솔루션이 제공하는 확장 성의 이점은 없습니다. 다형성 솔루션은 유추 switch진술 을 할 장소가 많은 경우에 유리 합니다. 하나만있을 것이라는 것을 확실히 알고 있다면, switch15 개의 하위 유형으로 재정의 된 함수와 15 개의 하위 유형에 의해 상속 된 기본 클래스를 디자인하여 인스턴스화하는 것보다 15 개의 사례가 있는 문장이 훨씬 간단 할 수 있습니다. 전체 시스템에서. 이러한 경우 새 하위 유형을 추가하는 case것은 하나의 함수에 명령문을 추가하는 것보다 훨씬 지루 합니다. 성능이 아닌 유지 관리 가능성에 대해 논쟁하고 싶습니다.switch 확장 성으로부터 이익을 얻지 못하는 특이한 경우의 진술.

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