카테고리 없음

[펌] fastcall

오늘도힘차게 2013. 2. 21. 17:51
728x90

역시 최호진님의 블러그에서 퍼왔습니다.
원문: http://pynoos.byus.net/tt/index.php?pl=133

아래의 글을 읽고, 언뜻 생각난것은 잘만 이용하면 성능튜닝시 사용하면 좋을듯 합니다.
특히 반복적인 호출이 일어나는 함수를 디자인할때 좋을듯 합니다.
-------------------------------------------------------------------


호출규약으로 번역되는 calling convention이라는 주제가 있다.
UNIX 쪽 C를 하는 사람들에게는 그다지 많이 다가오지 않는 주제일지 모르나, Windows 에서 프로그래밍을 하다보면, WINAPI라는 매크로를 사용할 때와 사용하지 않을 때가 있는 것을 볼 수 있는데, 저것은 실상 __stdcall 이라는 방식으로 선언하라는 것을 의미한다.

여기에는 중요한 두가지 요소가 있는데,

1. 인자 전달방식
2. 스택 청소 담당자

이다. 이런 차이에 의해 주위에서 많이 볼 수 있는 것이 다음 세가지이다.

1. cdecl
2. stdcall
3. fastcall

추가적으로 C++가 도입되면서 thiscall이라는 방식이 생겼지만, 이는 기본적으로 cdecl을 근간으로 하고 있으므로 생략하겠다. 또한 고생대의 pascal이나 far 등도 생략하겠다. 더불어 naked 로 선언되는 것도 생략한다.

cdecl과 stdcall은 다 안다고 가정하고... 흐흐흐...
요약정리하면, 스택을 비우는 책임이 cdecl은 호출자가 stdcall은 호출당한 놈이 있는데 그 차이가 있다. 이로 인해 가변인자를 처리하느냐(cdecl) 못하느냐(stdcall), 스택청소하는 코드가 곳곳에 산재하여 오브젝트 크기가 커지느냐(cdecl), 그렇지 않느냐(stdcall)의 차이로 구분할 수 있겠다.

요약한답시고 정리했지만 저게 다이므로 넘어가자. 내가 말하고 싶은 것은 fastcall이라는 것이다.
이 fastcall이라는 것은 내 기억상 borland c compiler에서 처음 도입되었다. 아니라면 지적해주시라.

아니, 도대체 뭐가 빠르단말이냐.

실상은 이렇다. 먼저 MSDN 문서를 살펴보면,
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_core___fastcall.asp

Argument-passing order:
The first two DWORD or smaller arguments are passed in ECX and EDX registers; all other arguments are passed right to left. 

아니, 이것은 스택을 이용하지 않고 레지스터를 이용하여 인자를 넘기겠다는 발상아니냐! 그렇다면, 메모리에 들어갔다 나갔다를 하지 않을 것이고, 게다가 스택을 청소하는 일도 없을 것 아닌가!
단, 두개까지만 허용한댄다.

재밌는 발상이다. 저 문서에서 더 발견할 수 있는 것은 funcion naming decoration이 추가적으로 일어난다는 것을 알 수 있다.
즉, dumpbin.exe 명령으로 확인할 수 있는 function의 심볼들 앞에 @로 시작하는 놈들은 모두 fastcall 이라는 것이다.

그렇다면, linux의 gnu c compiler에서는 어떠한가.
http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gcc/function-attributes.html
에서 살펴보면, __attribute__안에 fastcall을 넣을 수가 있덴다.
그래서 사용해보니..

warning: `fastcall' attribute directive ignored

라는 좌절스러운 말이 전해온다. 그러나! 좀 위쪽에 regparm이라는 것이 있다..하하..

void __attribute__((regparm(3))) fastfunction( int x, int y, int z )
{
}

이와 같은 방식으로 사용하면 된단다. regparm(N)으로 인자를 N으로 주면 몇개는 인자를 레지스터로 넘기는 것이다.
저 문서에서 살펴보면, EAX, EDX, ECX가 사용된덴다. 

심심하자나... 디스어셈블한번 해보자.

$ more a.c
#include 

void __attribute__((regparm(3))) fastfunction( int x, int y, int z )
{
        printf("%d %d %d\n", x, y, z);
}

int main()
{
        int x = 7;
        int y = 8;
        int z = 9;
        fastfunction( x, y, z );
        return 0;
}
야... 이거 gcc -O2 안해주면, fastcall 효과가 안난다.



$ gcc a.c -O2 -S
$ vi a.s

      1         .file   "a.c"
      2         .section        .rodata.str1.1,"aMS",@progbits,1
      3 .LC0:
      4         .string "%d %d %d\n"
      5         .text
      6         .p2align 4,,15
      7 .globl fastfunction
      8         .type   fastfunction, @function
      9 fastfunction:
     10         pushl   %ebp
     11         movl    %esp, %ebp
     12         subl    $24, %esp
     13         movl    %eax, 8(%esp)
     14         movl    $.LC0, %eax
     15         movl    %eax, 4(%esp)
     16         movl    stdout, %eax
     17         movl    %ecx, 16(%esp)
     18         movl    %edx, 12(%esp)
     19         movl    %eax, (%esp)
     20         call    fprintf
     21         movl    %ebp, %esp
     22         popl    %ebp
     23         ret
     24         .size   fastfunction, .-fastfunction
     25         .p2align 4,,15
     26 .globl main
     27         .type   main, @function
     28 main:
     29         pushl   %ebp
     30         movl    $7, %eax
     31         movl    %esp, %ebp
     32         subl    $8, %esp
     33         movl    $9, %ecx
     34         andl    $-16, %esp
     35         movl    $8, %edx
     36         call    fastfunction
     37         movl    %ebp, %esp
     38         xorl    %eax, %eax
     39         popl    %ebp
     40         ret
     41         .size   main, .-main
     42         .ident  "GCC: (GNU) 3.3.2"
대체 __attribute__((regparm(3))) 를 안쓰면 어떻게 되지?


      1         .file   "b.c"
      2         .section        .rodata
      3 .LC0:
      4         .string "%d %d %d\n"
      5         .text
      6 .globl fastfunction
      7         .type   fastfunction, @function
      8 fastfunction:
      9         pushl   %ebp
     10         movl    %esp, %ebp
     11         subl    $24, %esp
     12         movl    16(%ebp), %eax
     13         movl    %eax, 12(%esp)
     14         movl    12(%ebp), %eax
     15         movl    %eax, 8(%esp)
     16         movl    8(%ebp), %eax
     17         movl    %eax, 4(%esp)
     18         movl    $.LC0, (%esp)
     19         call    printf
     20         leave
     21         ret
     22         .size   fastfunction, .-fastfunction
     23 .globl main
     24         .type   main, @function
     25 main:
     26         pushl   %ebp
     27         movl    %esp, %ebp
     28         subl    $24, %esp
     29         andl    $-16, %esp
     30         movl    $0, %eax
     31         subl    %eax, %esp
     32         movl    $7, -4(%ebp)
     33         movl    $8, -8(%ebp)
     34         movl    $9, -12(%ebp)
     35         movl    -12(%ebp), %eax
     36         movl    %eax, 8(%esp)
     37         movl    -8(%ebp), %eax
     38         movl    %eax, 4(%esp)
     39         movl    -4(%ebp), %eax
     40         movl    %eax, (%esp)
     41         call    fastfunction
     42         movl    $0, %eax
     43         leave
     44         ret
     45         .size   main, .-main
     46         .ident  "GCC: (GNU) 3.3.2"
a.c의 fastfunction 내에서 printf에 대한 확장이 일어났었음을 감안해서 해석해야하는데, 위와 다른 것은 16(%ebp), 12(%ebp), 8(%ebp)로 접근하여 값을 가져온다는 것이다.

어, 이상하다? naming decoration (mangling)이 전혀 일어나지 않는다. 오브젝트만가지고 이것이 fastcall인지 알 수 있는 방법이 전혀없다.
MS의 오브젝트들은 dumpbin.exe의 심볼 덤프만으로 알 수 있는데 말이지.

그렇다면..? 장난한번 해볼까? 첫번째 인자가 eax로 넘어간다고 했으므로.. d.c 라는 프로그램을 다음과 같이 만들고


$ more d.c
#include 

void __attribute__((regparm(1))) fastfunction( int x )
{
        printf("%d\n", x );
}
그리고 c.c라는 파일을 다음과 같이 만들되, 그 안의 fastfunction은 일반함수처럼 생기도록 선언하여 위 함수와 링크되게 한다.


$ more c.c
#include 

void fastfunction( int x );

int main()
{
        printf("hi?\n");
        fastfunction( 0 );
        printf("hello?\n");
        fastfunction( 0 );
        printf("I am Hojin?\n");
        fastfunction( 0 );
        printf("Welcome to real world\n");
        fastfunction( 0 );
        printf("Help me Gandalf!\n");
        fastfunction( 0 );
        return 0;
}
원래 함수의 8 byte 이내의 크기를 가진 리턴값은 %eax에 저장되므로, printf의 return 값인 출력한 크기가 %eax에 남아 있게 되고 이것은 그대로 fastfunction에서 첫번째 인자로 동작할 것이다.

자, 위를 실행해보자..


$ ./a.out
hi?
4
hello?
7
I am Hojin?
12
Welcome to real world
22
Help me Gandalf!
17



728x90