switch와 함수 포인터의 안정성..

minij의 이미지

안녕하세요.
난생 처음 리눅스에서 작업을 하게 되어 KLDP에 가입해 많은 도움을 얻었습니다.
글을 올려보는건 처음이네요. ^^
오늘 데브피아 게시판을 보다가 재미있는 문제제기가 있어서,이곳에 올려 봅니다.
저도 나름대로 임베디드 관련 회사에 있다보니 상당히 흥미있는 내용이네요.
다음은 문제 제기입니다.

(문제)

1~4값을 입력받아 procA~procD함수를 호출하는 함수를 작성하시오.


-------------------------------------------------------------

(낙제점)

임베디드 제품에는 이렇게 짜면 나중에 제품 안전성 책임 못진다.

-------------------------------------------------------------

 

#define n_A (0)

#define n_B (1)

#define n_C (2)

#define n_D (3)

 

static void sub(int cmd)

{ 

    switch(cmd)

    {  case n_A : procA( ); break;

        case n_B : procB( ); break;

        case n_C : procC( ); break;

        case n_D : procD( ); break;

     }

}

 

 

 

-------------------------------------------------------------

팀장에게 싫은 소리 배부르게 먹고... 결국 팀장이 직접 작성해준 코드

(함수의 포인터를 썼다는 점에 주목)

-------------------------------------------------------------

 

typedef enum

{  n_A = 0,

    n_B,

    n_C,

    n_D

};

 

static void sub(int cmd)

{ 

     static const struct t_convtbl 

     {    int val;

           BOOL (* proc)(void);

      } convtbl[ ] = { { n_A, procA },

                            { n_B, procB },

                            { n_C, procC },

                            { n_D, procD }};

      if ((sizeof(convtbl)/sizeof(convtbl[0]))>cmd )

     { 

           (*convtbl[cmd].proc)();

     }

}

switch에서 case가 많아지면(몇 백개~) 컴파일러가 자동으로 함수 포인터로 변환해 최적화를 해준다고 알고 있습니다.
그러나 임베디드에 쓰이는 구형 컴파일러의 경우 최적화 작업이 없어서,
함수 포인터의 사용이 더 빠른 속도를 보장한다는 것이 요점인데요..

Visual C++ 컴파일러는 최적화 시에 switch문에서 점프할 위치를 아예 넣어주기 때문에, 오히려 switch가 빠르게 됩니다.
그런데, GCC(redhat linux 9.0 커널 2.4)에서는 어떨지 궁금합니다.
최적화가 될 것 같기는 한데, 어떤 방식으로 될 것인지... 최적화 후의 속도는 어떤 쪽이 빠를지 궁금하네요.

저도 이번 작업에 switch문이 몇 개 들어가고,
위 코드와 정확히 일치하는 용도의 코드도 한 군데 있습니다. ㅜ.ㅜ
많은 리플 주시면, 결정하는데 도움이 될 것 같습니다.

그럼 다들 좋은 하루 되세요. ^^

체스맨의 이미지

예제로 제시한 글이, 처음엔 안정성이라 언급했다가 뒤에는 성능으로 결론짓는 좀 이상한 글인 것 같습니다.

일반적으로 좋은 컴파일러들은, switch 문에 쓰인 상수값들에서 일정 갯수 이상 연속된 값들이 발견되면, 점프할 코드세그먼트 주소를 저장하는 배열을 만들고, 그 배열을 참조해서 즉시 점프합니다. 그래서, if 처럼 일일이 비교하지 않고, 함수 호출 오버헤드도 없어서, 위에서 함수 포인터를 쓴 경우보다 더 빠릅니다. gcc 최근 버젼들도 이 정도의 최적화는 거뜬히 합니다.

낙후된 컴파일러라서 이런 최적화가 안될 것 같이 예상되면 어셈블리 코드를 생성해서 확인해보는 게 좋은 방법일 것 같은데요.

Orion Project : http://orionids.org

M.W.Park의 이미지

minij wrote:
switch에서 case가 많아지면(몇 백개~) 컴파일러가 자동으로 함수 포인터로 변환해 최적화를 해준다고 알고 있습니다.

이거 정말 입니까? 대충 생각해봐도... 좀 문제의 소지내지는 예외가 많이 생길것같은 데요.
minij wrote:
그러나 임베디드에 쓰이는 구형 컴파일러의 경우 최적화 작업이 없어서,
함수 포인터의 사용이 더 빠른 속도를 보장한다는 것이 요점인데요..

저는 이렇게 쓰라고 권장하는 편입니다.
minij wrote:
Visual C++ 컴파일러는 최적화 시에 switch문에서 점프할 위치를 아예 넣어주기 때문에, 오히려 switch가 빠르게 됩니다.

이거도 정말입니까? 정말이라고 해도 더 빨라진다는 것은 좀 이상하네요.

컴파일러가 최적화하는 것에 의존하면 문제가 발생할 경우가 많습니다.
포팅을 할 때 특히 그렇구요.

-----
오늘 의 취미는 끝없는, 끝없는 인내다. 1973 法頂

minij의 이미지

M.W.Park wrote:
minij wrote:
switch에서 case가 많아지면(몇 백개~) 컴파일러가 자동으로 함수 포인터로 변환해 최적화를 해준다고 알고 있습니다.

이거 정말 입니까? 대충 생각해봐도... 좀 문제의 소지내지는 예외가 많이 생길것같은 데요.
minij wrote:
그러나 임베디드에 쓰이는 구형 컴파일러의 경우 최적화 작업이 없어서,
함수 포인터의 사용이 더 빠른 속도를 보장한다는 것이 요점인데요..

저는 이렇게 쓰라고 권장하는 편입니다.
minij wrote:
Visual C++ 컴파일러는 최적화 시에 switch문에서 점프할 위치를 아예 넣어주기 때문에, 오히려 switch가 빠르게 됩니다.

이거도 정말입니까? 정말이라고 해도 더 빨라진다는 것은 좀 이상하네요.

컴파일러가 최적화하는 것에 의존하면 문제가 발생할 경우가 많습니다.
포팅을 할 때 특히 그렇구요.

1. 저도 문제의 소지가 있다고 생각합니다.

2. 넵, 상식적으로 펑션 포인터가 빠른 것은 맞다고 생각합니다.

3. 넵, 다른 분이 테스트를 하셨습니다.
환경은 Windows XP, VC++ 7.1입니다.
타이머가 조금 긴가민가 하지만.. 빠른게 맞는 것으로 보입니다.

#include "stdafx.h"

 

#include <boost/progress.hpp>

#include <stdio.h>

 

using namespace std;

 

#define n_A (0)

 

#define n_B (1)

 

#define n_C (2)

 

#define n_D (3)

 

 

double ss;

 

void procA()

{

    ss = ss + 1;

}

 

void procB()

{

    ss = ss + 1; 

}

 

void procC()

{

    ss = ss + 1; 

}

void procD()

{

    ss = ss + 1; 

}

 

 

static void sub(int cmd)

{ 

 

    switch(cmd)

 

    {  case n_A : procA( ); break;

 

    case n_B : procB( ); break;

 

    case n_C : procC( ); break;

 

    case n_D : procD( ); break;

 

    }

 

}

 

 

enum

 

{  n_A_1 = 0,

 

n_B_1,

 

n_C_1,

 

n_D_1

 

};

 

 

 

static void sub2(int cmd)

 

{ 

 

    static const struct t_convtbl 

 

    {    int val;

 

    void (* proc)(void);

 

    } convtbl[ ] = { { n_A_1, procA },

 

    { n_B_1, procB },

 

    { n_C_1, procC },

 

    { n_D_1, procD }};

 

    if ((sizeof(convtbl)/sizeof(convtbl[0]))>cmd )

 

    { 

 

        (*convtbl[cmd].proc)();

 

    }

 

}

 

 

const int MAX = 100000000;

 

 

void test1()

{

    //printf(" 스위치문 테스트");

    boost::progress_timer t;

 

    for(int i=0; i < MAX; ++i)

    {

        sub(i%4);

    }

}

 

void test2()

{

    //printf(" 함수 포인터 테스트");

    boost::progress_timer t;

 

    for(int i=0; i < MAX; ++i)

    {

        sub2(i%4);

    }

}

 

int _tmain(int argc, _TCHAR* argv[])

{

 

    for(int i=0; i < 10 ; ++i)

    {

        test2();

 

        test1();

    }

 

    return 0;

}

 

 

 

이런 코드로 테스트 해보았으며

 

test2가  함수 포인터를 사용한 코드이고 test1이 switch문을 사용한 코드입니다

 

10번 평균   test2   :   1.03  s

                test1   :   0.39  s

 

로써 

 

test1의 #define And switch문을 쓴 코드가 훨씬 빨랐다

Aim high !

minij의 이미지

체스맨 wrote:
낙후된 컴파일러라서 이런 최적화가 안될 것 같이 예상되면 어셈블리 코드를 생성해서 확인해보는 게 좋은 방법일 것 같은데요.

동의합니다... 그런데 리눅스는 처음 접해서 환경이 많이 낯서네요.
으음.. 리눅스에서 어셈 코드를 보는 방법에 대해 가르쳐 주시면 정말 감사하겠습니다.

Aim high !

M.W.Park의 이미지

if ((sizeof(convtbl)/sizeof(convtbl[0]))>cmd ) 이거 빼고 해야 정확한 비교가 될 것같은데요. 실행시간 비교보다 어셈코드 비교를.... 8) gcc -S 해보세요.

-----
오늘 의 취미는 끝없는, 끝없는 인내다. 1973 法頂

minij의 이미지

M.W.Park wrote:
if ((sizeof(convtbl)/sizeof(convtbl[0]))>cmd )
이거 빼고 해야 정확한 비교가 될 것같은데요.
실행시간 비교보다 어셈코드 비교를.... 8)
gcc -S 해보세요.

음... 그렇군요.. 비교문 페널티가 있군요.
어셈 상으로도 스위치가 느릴 것은 없습니다.. 적어도 VC에서는.. ;;
원문의 답글에 달린 switch문의 어셈 코드는 다음과 같습니다.

扉の向こうへ (codecastle) wrote:
VC의 경우 switch문(case에서 순차적으로 증가하는경우)의 성능이 떨어지지않는 이유가 있네요
mov ecx,dword ptr [ebp-4] // ebp-4 = cmd
jmp dword ptr [ecx*4+401229h]
case별로 점프할 오프셋을 함수 아래쪽에 박아버리네요
이런식이라면 100개 이상이라도 성능에는 문제가 없겠네요
비교해보니 속도, 메모리 switch문이 우세하네요 적어도 VC++에서는...

gcc 옵션 매우*30 감사합니다. :D

Aim high !

ㅡ,.ㅡ;;의 이미지

switch 문이 안정성에 무슨문제가 있죠?ㅡ,.ㅡ;;
팀장님이 switch 문을 잘사용못하시는건 아니죠?..^^;;

switch와 후자의 방식은 장단점이 있겠죠.. 문제에서 만일 입력값이 날짜와같이
띄엄띄엄 나오는숫자면 어쩌죠? 만일문제에대해서만 생각한다면 구조체인자하나는 필요없고 구조체자체도 필요 없고..포인터배열정도면 되겠네요.


----------------------------------------------------------------------------

minij의 이미지

ㅡ,.ㅡ;; wrote:
switch 문이 안정성에 무슨문제가 있죠?ㅡ,.ㅡ;;
팀장님이 switch 문을 잘사용못하시는건 아니죠?..^^;;

switch와 후자의 방식은 장단점이 있겠죠.. 문제에서 만일 입력값이 날짜와같이
띄엄띄엄 나오는숫자면 어쩌죠? 만일문제에대해서만 생각한다면 구조체인자하나는 필요없고 구조체자체도 필요 없고..포인터배열정도면 되겠네요.

음.. 그렇죠.. 저도 퍼온거라.. 저기 나오는 팀장님이 어떤지는 잘 모르겠구요.. ^^;;
안정성이라는 것은 속도 측면을 말하는 것 같습니다.
임베디드 시스템의 경우 속도 측면에서 함수 포인터가 유리한 것은
사실입니다. (기계에서는 속도가 크리티컬하거든요...)
하지만 말씀하신 것처럼, 일반적인 시스템에서, 일반적인 입력값을 가정한다면,
switch가 유리할 것이라는데 저도 동의합니다. :)

Aim high !

newmania의 이미지

윗분들 말씀대로 switch 문이 table 로 구성된다면 많은 index 를 가질 때 속도면에서 유리할 수는 있다고 생각합니다. 하지만 많지 않은 index 일 때는 switch 로 구현하더라도 성능이 떨어지지 않는다고 생각합니다. 함수 pointer 로 실행하는 경우에 cmd 가 범위를 벗어날 경우 시스템에 치명적인 결과를 가져올 수 있습니다. 제 경우 멋지다고 생각되었던 코드들이 유지보수할 때에 걸리적거리는 경우가 많았던 것 같습니다. 수고하시고 좋은 하루 되세요. ^^;

pynoos의 이미지

저는 구현에 따라 다른 상황을 일반화시켜 호불호를 가려야하는 문제가 싫습니다만..

문제 해결이 switch 문이 함수포인터 벡터와 경쟁하는 상황이라면,
일단, switch를 사용하는 것은 컴파일러 혹은 최적화옵션을 선택할 수 밖에 없고,
함수 포인터를 벡터에 넣어서 점프하게 만드려면, 손수 최적화(?)하는 상황입니다.

다른 말로하면, 컴파일러가 뛰어난지 개발자가 뛰어난지 시합하는 것이죠.

문제를 다른 각도에서 정의하면, 이 문제는 case 로 구별할 정수들이 붙어 있는 상황에서만 가능하다는 것입니다.
심지어는 case에 참여하는 정수들이 1씩 증가하는 것들로만 이루어질지 모릅니다.
그렇지 않은 상황에서는 띄엄띄엄 값이 들어간 함수 배열을 만드는 것도 이상하고, case문이 최적화하기도 불쌍해 보일 것 같습니다.

경험상, switch 에 들어 있는 값들이 1씩 증가할 경우 최적화옵션이 켜질 경우 각 명령을 수행하기 위해 로컬에 작은 벡터를 하나 만들고 점프하는 디스어셈블 코드를 본적이 있습니다.

gcc가 어떻게 해줄 것인가를 답하는 것은 어떻게 보면 무의미하고, 유지보수측면에서 볼 때, 암시적인, 특히나
컴파일러의 행동에 따른 코드를 작성하는 경우라면, 되도록 명시적으로 알고리즘에 드러내도록 작성하는 것이 좋다고 생각합니다.

... 좀 횡설수설 했나요..?

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.