실전 STL 플밍.. 소스 참고..

// 하앍 include 가 넘 많다...
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>

#define EPOLL_SIZE 60
#define EPOLL_EVENT_SIZE 100
#define OPENPORT 5000

// main
int
main (int argc, char **argv)
{
 struct epoll_event *events;
 struct epoll_event ev;
 struct sockaddr_in addr, clientaddr;
 int clilen;
 int sfd, efd, cfd;
 int i;
 int max_got_events;
 int result;
 int readn;
 int sendflags = 0;
 char buf_in[256] = { '\0' };

 if ((efd = epoll_create (EPOLL_EVENT_SIZE)) < 0)
 {
  perror ("epoll_create (1) error");
  return -1;
 }
 
 // init pool
 events = (struct epoll_event *) malloc (sizeof (*events) * EPOLL_SIZE);
 if (NULL == events)
 {
  perror ("epoll_create (0) error");
  return -1;
 }

 clilen = sizeof (clientaddr);
 sfd = socket (AF_INET, SOCK_STREAM, 0);
 if (sfd == -1)
 {
  perror ("socket error :");
  close (sfd);
  return -1;
 }

 addr.sin_family = AF_INET;
 addr.sin_port = htons (OPENPORT);
 addr.sin_addr.s_addr = htonl (INADDR_ANY);
 if (bind (sfd, (struct sockaddr *) &addr, sizeof (addr)) == -1)
 {
  close (sfd);
  return -2;
 }
 listen (sfd, 5);
 
 if (sfd < 0)
 {
  perror ("init_acceptsock error");
  return 1;
 }

 printf("Running Server port %d\n",port);
 ev.events = EPOLLIN;
 ev.data.fd = sfd;
 result = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &ev);
 if (result < 0)
 {
  perror ("epoll_ctl error");
  return 1;
 }

 while (1)
 {
  max_got_events = epoll_wait (efd, events, EPOLL_SIZE, -1);
  for (i = 0; i < max_got_events; i++)
  {
   if (events[i].data.fd == sfd)
   {
    printf("Accepted\n");
    cfd = accept (sfd, (struct sockaddr *) &clientaddr, &clilen);
    if (cfd < 0)
    {
     perror ("Accept error");
     return -1;
    }

    ev.events = EPOLLIN;
    ev.data.fd = cfd;
    epoll_ctl (efd, EPOLL_CTL_ADD, cfd, &ev);
   }
   else
   {
    cfd = events[i].data.fd;

    memset (buf_in, 0x00, 256);
    readn = read (cfd, buf_in, 255);
    // if it occured ploblems with reading, delete from epoll event pool and close socket
    if (readn <= 0)
    {
     epoll_ctl (efd, EPOLL_CTL_DEL, cfd, &ev);
     close (cfd);
     printf ("Close fd ", cfd);
    }
    else
    {
     printf ("%s", buf_in);
     send (cfd, buf_in, strlen (buf_in), sendflags);
    }

   }
  }
 }
 return 1;
}

'리눅스 서버에 대해서' 카테고리의 다른 글

Epoll 채팅 서버 소스  (0) 2008.05.25
Select 채팅 서버  (0) 2008.05.25
쓰레드 에코서버  (0) 2008.05.25
epoll 서버 프로그래밍  (0) 2008.03.29
겜서버 숙제  (0) 2008.03.24

epoll 리눅스에서 다루는 이벤트 통지형 서버 구조임.
Win32로 따지면 WSAEventSelect 모델 정도로 볼수 있을것이다.
Win32에선 WSAEventSelect를 더욱 개선한 Overlapped 모델을 지원하지만 실제 Overlapped 모델은 윈도우 컨디션에 따라 성능을 제대로 보장하지 못한다고 한다.
하여튼..
http://twopairs.tistory.com/101 사이트에 의하면

c epoll vs java epoll vs java poll

korean

test machine : linux, mem 2G, 2cpu
server's business logic is echo, client send 1 request per 5 second and
 data volumn is less then 1k.

result :

c epoll server
when 30000 socket is running
CPU 6-12%
there aren't special phenomenon.

java epoll server
In 30000 socket is running
CPU 10-17%
there aren't special phenomenon.

java poll server
In 3000 socket is running
cpu reach near 100%
In 10000 socket is running
listen socket accept connection but cpu reach 100%...

conclusion :
epoll is fantastic

음.. 하여간 epoll 이면서 c언어로 해야한다.

서버는 c++을 사용하지 않는데 그 이유로는.. class를 생성하면서 메모리 관리가 복잡해지기 때문이다.
그리고. 직관적으로 짜지 않으면 워낙 많은 변수로 안전성이 떨어진다. ㅡ_ㅡ..

잡스 형님의 말씀 처럼 Simple is best 인 코드는 서버 코드가 아닐까 생각된다.

이제 본론에 들어가서 epoll 서버 샘플 프로그래밍이다. 출처는 아래와 같다.

출처 :
http://www.xevious7.com/archive/200704

epoll 서버 샘플 프로그래밍

프로그래밍/서버프로그래밍 2007/04/18 10:19

epoll 서버 샘플 프로그래밍  by xevious7   http://www.xevious7.com/52
(문서의 작성일. 2006.5월9일)
(문서의 수정및 추가 2007년 4월)

거의 1년여전에 이 문서를 작성해놓고 실수로 공개를 안하고 있었습니다.
계속 비공개문서로 있었던 것이죠. 아마도 무슨 바쁜일 때문에 미처 공개하지
못하고 다른 문서들로 인하여 계속 비공개로 남아있었던 것 같습니다.
최근에 epoll에 대한 것을 테스트하다가 뒤늦게 문서가 비공개로 되어있는
것을 알았습니다. 그래서  내용도 조금 수정하고 테스트서버도 작성해보았습니다.
소스도 공개합니다.

epoll을 이용하여 작성된 완성된 훌륭한 서버소스가 많이 공개되어있지만 ,
일반적으로 공부하려는 프로그래머가 그 큰 소스의 내부를 분석하여
이해하기는 좀 무리가 있다고 생각이 듭니다.

아무래도 잘 짜여지고 다듬어지다보니
보다 추상화가 많이 되어있고 보다 감싸져있기 때문에 쓰기는 편해도 이해하기는
힘들다고 할까요
.

직관적으로 이해를 돕기 위해서 , 여기 공개하는 epoll응용
테스트서버는 C언어로 작성하였습니다.  단순한 에코형서버입니다.
접속한 모든 클라이언트에게 내용을 전달합니다. 두 클라이언트가 접속하면
서로 채팅하는것이 될것이고 여러클라이언트가 붙으면 대화방처럼 작동합니다.

여기에 간단한 프로토콜을 넣으면 (채널관리 ,룸관리) 간단한 채팅서버가
될것입니다.

다시 말하면 이해를 위한 뼈대구조의 하나의 샘플입니다.


소스코드가 매끄럽지 못합니다. 다만 직관적 이해를 위해 코드를 압축하거나
추상화하는 것은 자제했습니다.

관련링크

http://www.joinc.co.kr/modules/moniwiki/wiki.php/epoll
윗글은 epoll의 제작자가 만든 원래의 URL에(man페이지) 대한 번역과
이 링크에도 간단한 에코서버소스가 공개되어있습니다.

epoll 제작자의 글은 다음 URL에 있습니다.
http://www.xmailserver.org/linux-patches/epoll.txt


대용량 서버에 대한 방법론에 대한것은 다음문서
http://www.kegel.com/c10k.html 에서 자세히 볼 수 있습니다.

epoll을 사용하기위한 조건

epoll은 2001년 7월 11일에 다비드 라이프니치(David Libenzi)씨에 의해서
리얼타임 시그날 (RealTime Sinal)의 대안으로 제안된 이후로 2.5.x 커널에 추가되기
시작하였습니다.

그리고 2.6.x 커널대에서는 기본으로 탑재되어 있습니다.

(2.6.x 커널은 2003년 12월에 릴리즈 되었습니다. 현재까지(2007년3월) 계속되고 있습니다.)
9.x 대 리눅스라면 epoll을 사용하는데 전혀 무리가 없다는 이야기입니다.
만약  2.6.x 커널 및의 리눅스라면 위의 joinc 문서에서 사용여부 가능방법과 라이브러리
등의 설치등등의 확인 방법이 설명되어있으니 참조하기 바랍니다.

자신의 리눅스 시스템에서 epoll이 사용가능한지 체크하는 C 코드

      #include <stdio.h>
      #include <stdlib.h>
      #include <sys/epoll.h>
     
      int main(int argc, char** argv)
      {
               int epoll_fd;
                 
               if ( (epoll_fd = epoll_create(500)) == -1 )
               {
                       printf("epoll 파일디스크립터 생성 오류 \n");
               }
               else
               {
                       printf("epoll 파일디스크립터 생성성공 \n");
                      close(epoll_fd);
                      /* 생성된 fd는 다른 fd와 마찬가지로 프로그램 종료전에 반드시 닫아주어야한다. */

               }
        }
        /* EOF */

컴파일이 안되거나 , 실행시 오류가 난다면 무엇인가 설정이 잘못되어있는 것입니다. 

epoll 을 사용하기 위한 API

epoll을 사용하기 위해서 알아야 될 API는 다음 세가지입니다.
int epoll_create(int size);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

함수의 각각의 설명은 man페이지나 제작자의 다음 링크에서 볼 수 있습니다.
http://www.xmailserver.org/linux-patches/epoll_create.txt
http://www.xmailserver.org/linux-patches/epoll_ctl.txt
http://www.xmailserver.org/linux-patches/epoll_wait.txt

한글번역문서는 jacking님의 일본man 페이지 번역이 다음링크에 있습니다.
http://jacking75.cafe24.com/Network/epoll.htm

위의 각각의 3개의 API의 이해가 소스를 이해하는데 필수적입니다. 당연한
이야기이지만요.

소스는 epoll LT모드를 사용하고있습니다. ( ET를 선언하지 않은경우
자동으로 LT입니다. 소스에 LT를 따로 선언하지 않았습니다.)
ET모드는 socket이 반드시 non blocking 소켓이어야 하지만 LT는
상관없습니다. 그래서 따로 non blocking 부분을 설정하는 부분이 없고
소켓 그대로 사용하였습니다. 또한 닫혀진 소켓에 대한 epoll set에서
삭제하는 부분도 없습니다.  이부분은 epoll 제작자의 FAQ에서 보면
닫힌 소켓에대해서 epoll set에서 자동으로 없어지는가 하는 질문에
닫힌 소켓은 자동으로 epoll set에서 없어진다라고 되어있기 때문에
따로 처리하지 않았습니다. 소켓이 닫히는순간 epoll set에서 사라집니다.

주석은 영어로 되어있습니다.  ^^;;

테스트서버 소스
/*-----------------------------------------------------------------

  epoll server test program by EuiBeom Hwang. : 2005-2007.
  Platform : Linux 2.6.x (kernel)
  compiler Gcc: 3.4.3.
  License: GNU General Public License  
  descption : simple test server. multi client accept.
              sample skelecton epoll structure(conceptional)
              this code is just sample implemention code.
------------------------------------------------------------------*/

/* header files */
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

/* definition */
#define MAX_CLIENT   10000
#define DEFAULT_PORT 9006
#define MAX_EVENTS   10000


/* global definition */
int g_svr_sockfd;              /* global server socket fd */
int g_svr_port;                /* global server port number */

struct {
         int  cli_sockfd;  /* client socket fds */
         char cli_ip[20];              /* client connection ip */
} g_client[MAX_CLIENT];

int g_epoll_fd;                /* epoll fd */

struct epoll_event g_events[MAX_EVENTS];

/* function prototype */
void init_data0(void);            /* initialize data. */
void init_server0(int svr_port);  /* server socket bind/listen */
void epoll_init(void);            /* epoll fd create */
void epoll_cli_add(int cli_fd);   /* client fd add to epoll set */

void userpool_add(int cli_fd,char *cli_ip);

/*--------------------------------------------------------------*/
/* FUNCTION PART
---------------------------------------------------------------*/

/*---------------------------------------------------------------
  function : init_data0
  io: none
  desc: initialize global client structure values
----------------------------------------------------------------*/
void init_data0(void)
{
  register int i;

  for(i = 0 ; i < MAX_CLIENT ; i++)
  {
     g_client[i].cli_sockfd = -1;
  }
}

/*-------------------------------------------------------------
  function: init_server0
  io: input : integer - server port (must be positive)
  output: none
  desc : tcp/ip listening socket setting with input variable
----------------------------------------------------------------*/

void init_server0(int svr_port)
{
  struct sockaddr_in serv_addr;
 
  /* Open TCP Socket */
  if( (g_svr_sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0 )
  {
      printf("[ETEST] Server Start Fails. : Can't open stream socket \n");
      exit(0);
  }

  /* Address Setting */
  memset( &serv_addr , 0 , sizeof(serv_addr)) ;

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  serv_addr.sin_port = htons(svr_port);

  /* Set Socket Option  */
  int nSocketOpt = 1;
  if( setsockopt(g_svr_sockfd, SOL_SOCKET, SO_REUSEADDR, &nSocketOpt, sizeof(nSocketOpt)) < 0 )
  {
      printf("[ETEST] Server Start Fails. : Can't set reuse address\n");
      close(g_svr_sockfd);
      exit(0);
  }
 
  /* Bind Socket */
  if(bind(g_svr_sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
  {
     printf("[ETEST] Server Start Fails. : Can't bind local address\n");
     close(g_svr_sockfd);
     exit(0);
  }

  /* Listening */
  listen(g_svr_sockfd,15); /* connection queue is 15. */

  printf("[ETEST][START] Now Server listening on port %d\n",svr_port);
}
/*------------------------------- end of function init_server0 */

void epoll_init(void)
{
  struct epoll_event events;

  g_epoll_fd = epoll_create(MAX_EVENTS);
  if(g_epoll_fd < 0)
  {
     printf("[ETEST] Epoll create Fails.\n");
     close(g_svr_sockfd);
     exit(0);
  }
  printf("[ETEST][START] epoll creation success\n");

  /* event control set */
  events.events = EPOLLIN;
  events.data.fd = g_svr_sockfd;

  /* server events set(read for accept) */
  if( epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, g_svr_sockfd, &events) < 0 )
  {
     printf("[ETEST] Epoll control fails.\n");
     close(g_svr_sockfd);
     close(g_epoll_fd);
     exit(0);
  }

  printf("[ETEST][START] epoll events set success for server\n");
}
/*------------------------------- end of function epoll_init */

void epoll_cli_add(int cli_fd)
{
 
  struct epoll_event events;

  /* event control set for read event */
  events.events = EPOLLIN;
  events.data.fd = cli_fd;

  if( epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, cli_fd, &events) < 0 )
  {
     printf("[ETEST] Epoll control fails.in epoll_cli_add\n");
  }

}

void userpool_add(int cli_fd,char *cli_ip)
{
  /* get empty element */
  register int i;

  for( i = 0 ; i < MAX_CLIENT ; i++ )
  {
     if(g_client[i].cli_sockfd == -1) break;
  }
  if( i >= MAX_CLIENT ) close(cli_fd);

  g_client[i].cli_sockfd = cli_fd;
  memset(&g_client[i].cli_ip[0],0,20);
  strcpy(&g_client[i].cli_ip[0],cli_ip);

}

void userpool_delete(int cli_fd)
{
  register int i;

  for( i = 0 ; i < MAX_CLIENT ; i++)
  {
       if(g_client[i].cli_sockfd == cli_fd)
       {
          g_client[i].cli_sockfd = -1;
          break;
       }
  }
}

void userpool_send(char *buffer)
{
  register int i;
  int len;

  len = strlen(buffer);

  for( i = 0 ; i < MAX_CLIENT ; i ++)
  {
      if(g_client[i].cli_sockfd != -1 )
      {
          len = send(g_client[i].cli_sockfd, buffer, len,0);
          /* more precise code needed here */
      }
  }
 
}


void client_recv(int event_fd)
{
  char r_buffer[1024]; /* for test.  packet size limit 1K */
  int len;
  /* there need to be more precise code here */
  /* for example , packet check(protocol needed) , real recv size check , etc. */

  /* read from socket */
  len = recv(event_fd,r_buffer,1024,0);
  if( len < 0 || len == 0 )
  {
      userpool_delete(event_fd);
      close(event_fd); /* epoll set fd also deleted automatically by this call as a spec */
      return;
  }

  userpool_send(r_buffer);

}

void server_process(void)
{
  struct sockaddr_in cli_addr;
  int i,nfds;
  int cli_sockfd;
  int cli_len = sizeof(cli_addr);

  nfds = epoll_wait(g_epoll_fd,g_events,MAX_EVENTS,100); /* timeout 100ms */

  if(nfds == 0) return; /* no event , no work */
  if(nfds < 0)
  {
      printf("[ETEST] epoll wait error\n");
      return; /* return but this is epoll wait error */
  }

  for( i = 0 ; i < nfds ; i++ )
  {
      if(g_events[i].data.fd == g_svr_sockfd)
      {
          cli_sockfd = accept(g_svr_sockfd, (struct sockaddr *)&cli_addr,(socklen_t *)&cli_len);
          if(cli_sockfd < 0) /* accept error */
          {
          }
          else
          {
             printf("[ETEST][Accpet] New client connected. fd:%d,ip:%s\n",cli_sockfd,inet_ntoa(cli_addr.sin_addr));
             userpool_add(cli_sockfd,inet_ntoa(cli_addr.sin_addr));
             epoll_cli_add(cli_sockfd);
          }
          continue; /* next fd */
      }
          /* if not server socket , this socket is for client socket, so we read it */
         client_recv(g_events[i].data.fd);   

  } /* end of for 0-nfds */
}
/*------------------------------- end of function server_process */

void end_server(int sig)
{
  close(g_svr_sockfd); /* close server socket */
  printf("[ETEST][SHUTDOWN] Server closed by signal %d\n",sig);
  exit(0);
}

int main( int argc , char *argv[])
{
 
  printf("[ETEST][START] epoll test server v1.0 (simple epoll test server)\n");
  /* entry , argument check and process */
  if(argc < 3) g_svr_port = DEFAULT_PORT;
  else
  {
     if(strcmp("-port",argv[1]) ==  0 )
     {
        g_svr_port = atoi(argv[2]);
        if(g_svr_port < 1024)
        {
           printf("[ETEST][STOP] port number invalid : %d\n",g_svr_port);
           exit(0);
        }
     }
  }


  init_data0();  

  /* init server */
  init_server0(g_svr_port);
  epoll_init();    /* epoll initialize  */

  /* main loop */
  while(1)
  {
     server_process();  /* accept process. */
  } /* infinite loop while end. */

}

출처 : Tong - e돌람바님의 UNIX/LINUX C/C++통

'리눅스 서버에 대해서' 카테고리의 다른 글

Epoll 채팅 서버 소스  (0) 2008.05.25
Select 채팅 서버  (0) 2008.05.25
쓰레드 에코서버  (0) 2008.05.25
밑의 epoll 예제가 좀 복잡해서 좀 간단하게 수정함  (0) 2008.03.29
겜서버 숙제  (0) 2008.03.24

쉬프트 연산과 곱셈


비트를 이동시키는 쉬프트 연산은 곱셈과 나눗셈의 대용으로 사용할 수 있다. 다음 예제를 실행해 보아라.

 

: shiftmulti

#include <Turboc.h>

 

void main()

{

     int i;

 

     printf("정수를 입력하세요 : ");

     scanf("%d",&i);

     printf("결과=%d\n",i << 1);

}

 

정수 i를 입력받은 후 왼쪽으로 한칸 쉬프트한 값을 출력했다. 실행해 보면 알겠지만 입력한 수의 정확하게 2배되는 값이 출력된다. 5를 입력하면 10, 100을 입력하면 200이 출력될 것이다. 정수를 왼쪽으로 한칸 쉬프트하면 두배가 되며 오른쪽으로 한칸 쉬프트하면 절반이 되는데 어째서 그런지 보자. 입력값이 5였다고 가정하면 이 값은 이진수로 0101인데 이 값을 10진수로 바꾸는 공식은 다음과 같다.

 

1*22+0*21+1*20 = 5

 

이진수의 각 자리수는 2의 거듭승에 해당하는 값을 표현한다. 그런데 비트를 왼쪽으로 한칸 이동시키면 모든 자리수의 지수가 1 올라가기 때문에 2배가 된다. 쉬프트한 결과는 1010이며 이 값은 다음과 같이 10진수로 바꿀 수 있다.

 

1*23+0*22+1*21+0*20 = 10

 

그래서 왼쪽으로 한칸 쉬프트하면 두배가 되는 것이다. 단, 용량 한계를 넘어서는 값은 잘려 나가는데 이는 어쩔 수 없다. 예를 들어 16비트 정수 40000을 왼쪽으로 쉬프트하면 80000이 되는데 16비트로는 이 값을 표현할 수 없으므로 제일 오른쪽의 1이 밀려나고 결과는 14464가 된다. 이 값은 80000-65536인데 밀려나서 버려진 오른쪽 비트의 값이 65536이기 때문이다. 32비트의 int형을 쓰면 웬만큼 큰 수라도 용량의 한계를 넘지 않을 것이다.

왼쪽으로 쉬프트하는 연산이 값을 두배로 만드는데 비해 오른쪽으로 쉬프트하는 연산은 값을 절반으로 만든다. 모든 자리수의 지수가 1 감소하기 때문이다. 이진수 1100을 오른쪽으로 쉬프트하면 0110이 되는데 이 값은 처음값 12의 절반인 6이다. 동일한 원리이므로 따로 그림까지 그릴 필요는 없을 것 같다.

오른쪽 쉬프트는 자리 넘침이 발생하지 않지만 오른쪽으로 밀려나 사라지는 값만큼 오차가 생길 수는 있다. 짝수를 밀면 정확하게 절반이 되지만 홀수를 밀면 제일 오른쪽에 있던 1이 밀려 나므로 실제값보다 0.5더 작은 값이 계산된다. 예를 들어 이진수 0111(7)을 오른쪽으로 밀면 b0가 밀려나고 0011(3)이 된다. 정확하게 계산하자면 3.5가 되어야 하지만 비트는 정수의 세계이기 때문에 밀려난 0.5는 사라지게 된다.

좌우 쉬프트 연산이 곱셈과 나눗셈 대용으로 사용될 수 있다는 것이 도저히 이해가 안간다면 아마도 2진수에 익숙하지 않아서 그럴 것이다. 그렇다면 10진수로 이 현상을 설명해 보자. 10진수 1234에서 각 자리수는 10의 거듭승에 해당하는 자리값을 가지고 있다. 1은 103인 1000자리에 있고 2는 102인 100자리에 있고 3은 101인 10자리에 있는 셈이다.

 

1234=1*103+2*102+3*101+4*100

 

이 값을 왼쪽으로 쉬프트하면 각 자리수의 지수가 1증가한 12340이 된다.

 

12340=1*104+2*103+3*102+4*101+0*100

 

천자리는 만자리로 올라가고 백자리는 천자리로 올라가므로 모든 값이 10배가 되어 전체값도 10배가 되는 것이다. 반대로 오른쪽으로 밀면 모든 자리수가 10배 감소하므로 10분의 1로 값이 줄어든다. 1234를 오른쪽으로 밀면 123이 되고 일자리에 있던 4는 밀려나 사라진다. 물론 실수 차원이라면 123.4가 되겠지만 말이다.

좀 더 쉽게 설명하자면 임의의 십진수가 있을 때 뒤에 0을 하나 더 붙이면 10배가 되고 제일 뒷자리를 제거해 버리면 약간의 오차를 제외하고 1/10로 그 값이 줄어든다. 초등학생들도(심지어 일부 유치원 아동들까지도) 아는 간단한 원리이며 일상 생활에서도 흔히 있는 연산이므로 너무 너무 당연하게 생각될 것이다. 10진수에서 쉬프트 연산이 10배씩 증감하는 것처럼 2진수에서는 쉬프트 연산이 2배씩 증감하는 것이다.

왼쪽으로 한칸 밀면 두 배가 된다. 그럼 이 수를 다시 왼쪽으로 한칸 밀면 2배의 2배, 즉 4배가 될 것이다. 다시 한 번 더 밀면 8배가 된다. 자, 그럼 이제 쉬프트 연산과 곱셈 연산의 관계를 일반화해 보자.

 

a << b == a * 2b

 

a를 b만큼 왼쪽으로 민다는 것은 a를 2의 b승만큼 곱하는 것과 같다. 이 공식은 b가 음수일 때도 똑같이 적용된다. a를 -1만큼 왼쪽으로(즉 오른쪽으로 한칸) 밀면 2-1를 곱하는(즉 2로 나누는)것과 같다. 그래서 곱셈 대신에 쉬프트 연산을 사용할 수 있는데 이 두 연산은 엄청난 속도 차이가 있다. 비트를 이동시키는 것과 일정 횟수 더하기를 반복하는 것은 CPU 입장에서 보면 완전히 다른 작업이기 때문에 속도차가 무려 10배 정도 난다. 쉬프트 연산은 전혀 논리적이지 않으며 기계적이므로 기계가 하기에는 아주 쉬운 연산인 것이다.

a*2를 한 번 할 시간이면 a << 1을 10번 정도 할 수 있다는 얘기다. 이렇게 속도차가 나기 때문에 핵심 게임 엔진이나 시스템 프로그래머들은 곱셈 대신 쉬프트 연산을 즐겨 사용한다. 중요한 루프에서 곱셈을 하느냐 쉬프트 연산을 하느냐에 따라 프로그램의 성능에 확연한 차이가 나기 때문에 쉬프트 연산은 도저히 피할 수 없는 달콤한 유혹인 것이다.

쉬프트 연산이 곱셈에 비해 불리한 점은 2의 거듭승에 대해서만 곱셈이 가능하다는 점이다. 2배, 4배, 8배, 16배 등만 할 수 있으며 3배, 17배 이런 연산은 할 수 없다. 그러나 쉬프트 연산과 덧셈, 뺄셈을 잘 조합하면 이런 연산이 가능해지기도 한다.

 

3배 : a << 1 + a;

9배 : a << 3 + a;

15배 : a << 4 - a;

60배 : a << 6 - a << 2;

 

특히 제일 마지막의 쉬프트 연산으로 60배를 하는 코드는 아주 기발한 응용예이며 감탄을 금하기 어려운 예술 코드라고 할 수 있다. 정밀하게 측정해 보면 이런 연산들이 곱셈보다 수배~수십배 더 빠르다. 보통의 경우라면 일반적인 곱셈을 하는 것이 소스를 유지하기에 편리하지만 속도가 지극히 중요하다면 곱셈보다는 가급적이면 쉬프트 연산을 사용하는 것이 좋다.

'C/C++언어' 카테고리의 다른 글

고정 소수점  (0) 2008.05.09
각 언어별 3중 포인터  (0) 2008.05.06
[리눅스프로그래밍] makefile  (1) 2008.03.07
ASSERT(), VERIFY(), TRACE()  (0) 2008.03.04
템플릿 사용한 max 만들기  (0) 2008.03.03
이거 할때마다 느끼지만 조낸 빡세다... orz

+ Recent posts