지난 일요일에 신도림역에 위치한 뽀로로 테마파크를 방문했다. 이제 17개월 된 아이가 조금은 뽀로로를 인지한 것같아서 데리고 갔는데 인지하고 안하고를 떠나서 너무 좋아하는 것이었다.

위치는 신도림역 하차하여 외부로 나가지말고 1번출구 방향으로 이동하면 디규브 시티 백화점 지하1층으로 갈수있다 거기서 엘리베이터를 타고 4층으로 가면 된다.
주의!! 유모차타고 지상으로 올라가서 1번출구로 가면 돼지~ 하는생각을 하고 2번출구 쪽에만 있는 엘리베이터를 타고 올라가면 1번출구로 가는 방법찾기가 쉽지않고 그냥멀다! 그렇게 가면 더 고생한다.

일단 1번 출구방향으로 가면


디큐브시티가 보인다. 보이는길 따라 가면 입구가있고 엘리베이터를 잘 찾아서(?) 4층으로 가자.

4층 엘리베이터를 내리면 일단 뽀로로 부위기가 많이 난다. 입구를 보면,


줄을 설수있다. 아이가 자고있어서 들어가기전에 주위를 돌아보니


포토존 처럼보이는 곳에 포비가 인사를 한다.

입장료에 대한 내용은 기억나는데로 정리하면,
   1. 2시간 기본이고 어른 6천원 아이가 1만 6천원이다. 2시간 이후 십분당 어른 5백원 아이 천원이다. 비싸긴하다
   2. 18개월미만 아이들은 50% 할인이다. 할인율이 센만큼 애매한 아이는 증명할만한 서류(저는 등본 출렸했음)를 갖고가야한다.
   3. 입구 쪽에 컴퓨터가 있는데 멤버쉽가입하면 첫방문시 20% 할인이고 다음부터는 10% 할인이다.


여기까지이고 시간은 대략 오전 10:30 분 부터 오후 8:30까지로 봤다. 내가 11시반 쯤에 갔는데 점심시간이라 그런지 한산했다. 1시반에서 2시에 입장하는 사람들이 엄청많았다. 일찍가서 놀고 빠지는 것이 좋을 것 같다.

입장하자 마자 정면에 보이는 ?? 뭘라불러야 할지..


이 넓은 공간을 뛰어 놀다 미끄럼틀을 타고 공이있는 곳으로 이동이된다.

일단 뽀로로네 집으로 가보자. 아이랑 둘이서 가서 사진찍을 타이밍을 잘잡지 못했다.





들어가면 티비에서 보는 방이랑 거의 똑같다.


주위를 살펴보면
카페가 있는데 공사중이었다



크롱이 있는 쪽으로 가면 간단한 장난감 정도 있다.

뽀로로 친구들 집에서 컵케익도 만들고 하는 이벤트등이 있지만 나이제한이 있다.

기차도 타보자. 시간이 정해져 있으니 잘보고 타자.


짧게 두바퀴 돈다.

그리고 흔히 키즈카페있는 놀이 시설이 있는데 조금크다는것이 좋다.

중간에 갑작스런 뽀로로 등장! 질서정연히 아이들은 같이 춤을 춘다
더 많은 볼꺼리와 놀이시설이 있었으나 너무 힘들어서 2시간을 못채우고 나왔다. 가족들과 가기 좋은곳이다 백화점에서 쇼핑도 할수있다.



'Mobile ' 카테고리의 다른 글

자연 캠핑장 - 과천  (1) 2013.10.08
서울 대공원 - 동물원  (0) 2013.10.05
Mobile 글쓰기  (0) 2011.07.28

define 으로  do { ... } while(0) 많이 쓰는 이유


Kernel code 를 보면 do { // some codes.. } while(0) 를 많이 쓰고 있다.


이에 대한 이유를 보면,

   1. 빈 구문은 compiler 로 부터 왜 #define FOO do {} while(0). 같이 썼는지 warning 을 받을 수 있다.

      (뭔가 구현 중에 있는 code 나 특정 define 이 정의되지 않을 때의 code가 없는 경우에 대체 후, compile 시 알림 같은 것으로 사용이 되려나.. 싶다.)

   2. 지역 변수 선언을 위해 쓰는 기본 block 으로 사용될 수 있다.

   3. 복잡한 macro를 이용해서 조건부 코드를 만들려고 할 때, 아래와 같이 만들 수 있을 것이다. 이것에 문제점을 보안하기 위해 do-while-0 블락을 사용할 것이다.

#define FOO(x) \
        printf("arg is %s\n", x); \
        do_something_useful(x);

위의 define으로 조건부 code를 만들어보자. 예를 들어,

if (blah == 2)
        FOO(blah);

위에서 처럼 만들었다면, interpreter 는 FOO 를 정의된 내용으로 대체를 할 것이다.

if (blah == 2)
        printf("arg is %s\n", blah);
        do_something_useful(blah);;

위에서 보듯이 printf 와 do_something_useful() 함수를 같이 쓰려는 목적과는 다르게, blah 가 2일 경우에만 printf 와 do_something_useful 가 같이 불릴 것이다. 이를 예방하기 위해, do { ... } while(0) 으로 define을 해두면,

if (blah == 2)
        do {
                printf("arg is %s\n", blah);
                do_something_useful(blah);
        } while (0);

위와 같이 변경 될 것이고 한번에 호출 될 수 있을 것이다.


  4. 또 다른 예제로는, define으로 지역변수 선언과 사용되는 것을 만들어 놓으면 아래와 같이 구현이 될 것인데(물론 여기에 나온 예제는 간단하고 억지스러움이 있을 수 있다.)

#define exch(x,y) { int tmp; tmp=x; x=y; y=tmp; }

위의 정의를 아래와 같이 사용한다면,

if (x > y)
        exch(x,y);          // Branch 1
else  
        do_something();     // Branch 2

이런식으로 구현을 하려고 할 것이고 이는 컴파일에 문제가 발생할 것이다. 이유는, 아래와 같이 번역(?) 되기 때문이다.

if (x > y) {                // Single-branch if-statement!!!
        int tmp;            // The one and only branch consists
        tmp = x;            // of the block.
        x = y;
        y = tmp;
}
;                           // empty statement
else                        // ERROR!!! "parse error before else"
        do_something();

세미콜론(;) 이 블락이 끝나자 마자 오게되어 발생하는 문제이다. 이것의 해결또한 do { ... } while(0) 으로 해결 할 수 있단다.

if (x > y)
        do {
                int tmp;
                tmp = x;
                x = y;
                y = tmp;
        } while(0);
else
        do_something();

위에서 보듯이 정리가 될 것이다. 이는 습관적인(?) rule 을 통해 code에 문제를 해결하기 위한 것이라 생각이 든다. 이미 kernel에는 많은 do { ... } while(0) 있다. 


추가적으로 GCC 는 do-while-0 블락을 대체 할 수 있는 것을 제공한다. 아래와 같이 사용하면 위에서 쓰는 것과 동일한 효과를 보는 것이다.(참고 자료 : http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Statement-Exprs.html#Statement-Exprs)

#define FOO(arg) ({         \
           typeof(arg) lcl; \
           lcl = bar(arg);  \
           lcl;             \
    })

알아두면 편리한 code 작성 법이다. 원본 URL 은 http://kernelnewbies.org/FAQ/DoWhile0 이다.


Likely and Unlikely


Likely() 와 Unlikely() 의 사용법과 용도를 알아보자. 원본 URL : http://kernelnewbies.org/FAQ/LikelyUnlikely


이 함수들은 뭐하는 건가?


예제를 보자.

bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx);
if (unlikely(!bvl)) {
  mempool_free(bio, bio_pool);
  bio = NULL;
  goto out;
}

위와 같이, 특정 condition 을 확인하는 용도로 사용되는데 위의 code는 bvec_alloc으로 할당 받고 bvl 이 유효하지 않는 address이라면 free 하고 NULL 로 만들어주는 code이다. 


likely(), unlikely() 는 include/linux/compiler.h 에 정의된 macro 이다. 이것의 용도는 컴파일러에게 branch 예측을 도와 주는 용도로 사용이된다. 즉, 대부분 0으로 예측이 된다면 unlikely(x) 의 형태로 쓰고, 1로 예상되는 값을 likely(x) 로 쓴다. 예측을 도와 줌으로써 성능의 향상을 볼 수 있도록 하는 것이다.


#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

__builtin_expect() 함수는 GCC 문서의 설명을 살펴보면,(원본은 http://kernelnewbies.org/FAQ/LikelyUnlikely 여기에서 보시길)

-- Built-in Function: long __builtin_expect (long EXP, long C)

'__builtin_expect' 를 사용하는 것은 compiler 에게 branch prediction 정보를 제공해주기 위한 것이다. 일반적으로 개발자들은 자신의 program이 실제로 어떻게 수행되는지 알기가 힘들기 때문에 '-fprofile-arcs' option을 통해 profile 을 feedback 받는 것을 선호합니다. 그렇지만, 어떤 application들은 이것 조차도 수집하기 힘든경우가 있긴 하다.


반환 값은(return value)는 EXP 인 완전한(?) 표현이어야 한다.(아래의 예제를 통해..) EXP와 C는 서로 같아야 합니다.(EXP == C)


if (__builtin_expect (x, 0))

foo ();

이렇게 구현한 것의 의미는 foo() 라는 함수가 불리지 않기를 기대하는 것입니다. 즉, 'x' 는 대부분 0 의 값을 가졌으면 한다는 것이다. 또 다른 방법으로는 EXP 에 "식"으로 사용할 수 있다.


if (__builtin_expect (ptr != NULL, 1))

error ();

예제가 조금 이상하다. ptr != NULL 인 경우가 대부분이라고 알려주는 것인데, NULL 이 아닐때는 error() 가 실행된다. 아닌 경우라면 bypass.

아래의 예제를 보자.
#define likely(x)    __builtin_expect(!!(x), 1)
#define unlikely(x)  __builtin_expect(!!(x), 0)

int main(char *argv[], int argc)
{
   int a;

   /* Get the value from somewhere GCC can't optimize */
   a = atoi (argv[1]);

   if (unlikely (a == 2))
      a++;
   else
      a--;

   printf ("%d\n", a);

   return 0;
}

위의 code를 편집기에 붙여 넣고 저장 한뒤,(test.c)

$ gcc -o test test.c -O2

$ objdump -S test

하면 많은 내용들이 나오는데 그 중에 <main> 이라는 부분을 찾아보면

080483b0 <main>:
 // Prologue
 80483b0:       55                      push   %ebp
 80483b1:       89 e5                   mov    %esp,%ebp
 80483b3:       50                      push   %eax
 80483b4:       50                      push   %eax
 80483b5:       83 e4 f0                and    $0xfffffff0,%esp
 //             Call atoi()
 80483b8:       8b 45 08                mov    0x8(%ebp),%eax
 80483bb:       83 ec 1c                sub    $0x1c,%esp
 80483be:       8b 48 04                mov    0x4(%eax),%ecx
 80483c1:       51                      push   %ecx
 80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>
 80483c7:       83 c4 10                add    $0x10,%esp
 //             Test the value
 80483ca:       83 f8 02                cmp    $0x2,%eax
 //             --------------------------------------------------------
 //             If 'a' equal to 2 (which is unlikely), then jump,
 //             otherwise continue directly, without jump, so that it
 //             doesn't flush the pipeline.
 //             --------------------------------------------------------
 80483cd:       74 12                   je     80483e1 <main+0x31>
 80483cf:       48                      dec    %eax
 //             Call printf
 80483d0:       52                      push   %edx
 80483d1:       52                      push   %edx
 80483d2:       50                      push   %eax
 80483d3:       68 c8 84 04 08          push   $0x80484c8
 80483d8:       e8 f7 fe ff ff          call   80482d4 <printf@plt>
 //             Return 0 and go out.
 80483dd:       31 c0                   xor    %eax,%eax
 80483df:       c9                      leave
 80483e0:       c3                      ret

이것도 원본 URL 에서 붙여 넣은 것인데, 직접 해보니 조금 짤린 부분이 있는 것으로 보인다. a == 2가 같은 것이 참이라면 0x80483e1 으로 jump 를 하게 되는데 jump 되는 주소의 instruction 이 복사가 안된 듯 하다. 거기에 mov command 로 a++ 인 "3"을 직접 넣어주고 printf 를 호출해야 하니, 80483d0의 주소로 바로 jump 하는 instruction 이 있을 것이다.


주석에서 보듯이, unlikely() 의 경우 a의 값과 2가 같지 않는것이 대부분이라고 해준것이다. 만약 2와 같지 않으면 jmp instruction 을 수행하지 않고 pipeline flush 가 일어나지 않도록 하여 성능 향상을 주는 것이다.


반대로 likely() 로 변경하여 진행해보자.

컴파일을 다시 하고, objdump 로 disassem 해보면,


080483b0 <main>:
 //             Prologue
 80483b0:       55                      push   %ebp
 80483b1:       89 e5                   mov    %esp,%ebp
 80483b3:       50                      push   %eax
 80483b4:       50                      push   %eax
 80483b5:       83 e4 f0                and    $0xfffffff0,%esp
 //             Call atoi()
 80483b8:       8b 45 08                mov    0x8(%ebp),%eax
 80483bb:       83 ec 1c                sub    $0x1c,%esp
 80483be:       8b 48 04                mov    0x4(%eax),%ecx
 80483c1:       51                      push   %ecx
 80483c2:       e8 1d ff ff ff          call   80482e4 <atoi@plt>
 80483c7:       83 c4 10                add    $0x10,%esp
 //             --------------------------------------------------
 //             If 'a' equal 2 (which is likely), we will continue
 //             without branching, so without flusing the pipeline. The
 //             jump only occurs when a != 2, which is unlikely.
 //             ---------------------------------------------------
 80483ca:       83 f8 02                cmp    $0x2,%eax
 80483cd:       75 13                   jne    80483e2 <main+0x32>
 //             Here the a++ incrementation has been optimized by gcc
 80483cf:       b0 03                   mov    $0x3,%al
 //             Call printf()
 80483d1:       52                      push   %edx
 80483d2:       52                      push   %edx
 80483d3:       50                      push   %eax
 80483d4:       68 c8 84 04 08          push   $0x80484c8
 80483d9:       e8 f6 fe ff ff          call   80482d4 <printf@plt>
 //             Return 0 and go out.
 80483de:       31 c0                   xor    %eax,%eax
 80483e0:       c9                      leave
 80483e1:       c3                      ret

여기도 마지막 두 line 이 짤려져 있다. jne 로 jump 하게 되면 거기에 2 라는 값을 eax 에 넣어주고 printf 를 호출 하도록 하는 code일 것이다. 

위에서 보듯이 likely() 로 하면 대부분 2와 같을 것이라고 컴파일러에게 알려줘서 최대한 jmp instruction 이 수행되지 않게 될 것이다.


이렇게 개발자가 예측할 수 있는 부분에 대해 거의 대부분이 x와 같을 것이다 혹은 다를 것이다라는 것을 컴파일러에게 알려주는 방식으로 적용하여 program을 만들 수 있을 것이다.



+ Recent posts