- 차례
- 1절. 소개
- 2절. IPC 테스트
- 1절. 소개
-
- 2.1절. 테스트할 IPC 설비의 종류
- 2.2절. 테스트할 내용
- 2.3절. 테스트 방법
- 2.4절. 테스트 시스템 환경
- 2.5절. IPC 별 테스트
- 2.2절. 테스트할 내용
-
- 2.5.1절. Unix Domain Socket
- 2.5.2절. FIFO
- 2.5.3절. Message Queue
- 2.5.2절. FIFO
- 2.5.1절. Unix Domain Socket
- 2.6절. 테스트 결과
-
- 2.6.1절. 테스트 결과 1 (71 바이트)
- 2.6.2절. 테스트 결과 2 (512 바이트)
- 2.6.1절. 테스트 결과 1 (71 바이트)
- 2.7절. IPC 성능 테스트결과에 대한 분석
- 2.8절. 무엇을 선택하는게 좋을까?
- 2.1절. 테스트할 IPC 설비의 종류
1절. 소개
IPC 는 매우 다양한 종류가 존재하며, 각 IPC 설비 종류마다 장/단점을 가지고 있다. 그러므로 어떤 일을 하는 프로그램들이냐에 따라서 거기에 적당한 장점을 가지고 적당한 성능을 보여주는 IPC 설비를 선택해주어야 한다. 이 글은 이러한 IPC 설비에 대한 간단한 성능 테스트에 관한 글이다.
2절. IPC 테스트
2.1절. 테스트할 IPC 설비의 종류
모든 IPC 에 대해서 테스트를 하지는 않을것이다. 여러가지 IPC 설비중에서, 서버/클라이언트 모델구성이 가능한 IPC 설비를 선택해서 테스트 할것이다.
이번 테스트에서는 위의 서버/클라이언트 모델의 구성이 비교적 용이한, message queue, Unix Domain Socket, FIFO 3가지 IPC 에 대한 테스트를 실시하기로 결정했다.
2.2절. 테스트할 내용
테스트할 내용은 process 의 실행시간이며, real time, user time, sys time 3가지 부분에서 테스트가 진행될것이다. 그렇긴 하지만 우리가 가장 관심있어 할 부분은 real time 관련된 부분이 될것이다.
2.3절. 테스트 방법
우선 3가지 테스트할 IPC 에 대한 서버/클라이언트 프로그램을 작성할것이다. 서버측은 각각의 IPC 선로를 만들고, 클라이언트측에서 데이타를 만들어서 IPC 선로를 통해서 서버측으로 전송한다. 우리는 데이타를 모두 전송하는데 걸리는 시간을 테스트 하게 될것이다.
+--------+ packet +--------+ | Client | ------------------------------> | Server | +--------+ +--------+ |
Process 시간을 체크하기 위해서 time(1) 명령어를 사용할것이다.
사실 time 명령어를 사용할경우 정확한 process 시간을 체크할수는 없을것이다. 실제 처리하고자 하는 데이타 전송 루틴외의 다른 부분들 예를들어 프로세스를 시작하고 종료하는데 드는 시간, 각종 IPC 설비 초기화와 관련된 시간들, 그밖의 자잘한 부분들에서 드는 시간들이 덤으로 추가되기 때문이다.
어쨋든 그러한 어느정도의 추가적인 시간들은 무시하기로 했다. 실제 체크하고자할 전송영역의 실행시간을 크게 만들면(보내고자 하는 메시지의 양을 크게) 하면, 상대적으로 그러한 추가적인 시간들은 무시할수 있을거라고 생각했기 때문이다. 그리고 그렇게 아주 세밀한 성능측정까지는 필요 하지 않았기 때문이다.
만들어진 데이타를 시각적으로 그럴듯하게 보여주기 위해서 그래프를 그리기로 했으며, 그래프를 그리는데는 gnuplot 를 사용했다. gnuplot 에 대한 내용은 GnuPlot 문서를 참고하기 바란다.
테스트는 각 IPC 설비들에 대해서 71 바이트씩 보내는 테스트로 5번씩, 512 바이트씩 보내는 테스트로 5번씩 실시했다.
2.4절. 테스트 시스템 환경
이러한 테스트는 시스템 환경의 영향을 크게 받으므로 테스트를 실시하기 전에 테스트 시스템의 환경에 대해 정리해 보았다.
표 1. 테스트 환경
운영체제 | Linux(kernel 2.4.13) |
CPU | Intel PIII 700 |
RAM | 256M |
2.5절. IPC 별 테스트
이번장에서는 각 IPC 테스트를 위한 서버/클라이언트 프로그램의 코드와 실제 테스트 방법에 대해서 설명하도록 하겠다.
2.5.1절. Unix Domain Socket
unix_domain_server.c
#include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> int main(int argc, char **argv) { int server_sockfd, client_sockfd; int state, client_len; FILE *fp; struct sockaddr_un clientaddr, serveraddr; int packet_size; char *buf; if (argc != 3) { printf("Usage : ./unix_domain_server [filename] [packet size]\n"); exit(0); } packet_size = atoi(argv[2]); buf = (char *)malloc(packet_size); if ((server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("socket error : "); exit(0); } unlink(argv[1]); bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sun_family = AF_UNIX; strcpy(serveraddr.sun_path, argv[1]); state = bind (server_sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); if (state == -1) { perror("bind error : "); exit(0); } state = listen(server_sockfd, 5); if (state == -1) { perror("listen error : "); exit(0); } printf("accept :\n"); client_sockfd =accept(server_sockfd, (struct sockaddr *)&clientaddr, &client_len); while(1) { read(client_sockfd, buf, packet_size); } close(client_sockfd); exit(0); } |
unix_domain_client.c
#include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> int main(int argc, char **argv) { int server_sockfd, client_sockfd; int state, client_len; int fd_r; int n; pid_t pid; FILE *fp; struct sockaddr_un clientaddr, serveraddr; int packet_size; char *packet; int total_size = 100000000; int loop_size; int i; packet_size = atoi(argv[2]); printf("packet size %d\n", packet_size); loop_size = total_size / packet_size; packet = (char *)malloc(packet_size); memset(packet, 0x00, packet_size); memset(packet, '0', packet_size); packet[packet_size-1] = '\n'; if (argc != 3) { printf("Usage : ./unix_domain_server [filename] [packet size]\n"); exit(0); } if ((client_sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { perror("socket error : "); exit(0); } bzero(&clientaddr, sizeof(serveraddr)); clientaddr.sun_family = AF_UNIX; strcpy(clientaddr.sun_path, argv[1]); client_len = sizeof(clientaddr); if (connect(client_sockfd, (struct sockaddr *)&clientaddr, client_len) < 0) { perror("connect error : "); exit(0); } for (i = 0; i < loop_size; i++) { write(client_sockfd, packet, packet_size); } close(client_sockfd); exit(0); } |
[root@localhost test]# ./unix_domain_server ... [root@localhost test]# time ./unix_domain_client packet size 512 real 0m2.958s user 0m0.120s sys 0m1.840s |
위와 같은 테스트는 5번에 걸쳐서 이루어 지게 되며, 그 결과를 기록한다.
2.5.2절. FIFO
fifo_server.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { int fd_w, fd_r; int n; int packet_size; char *buf_r; packet_size = atoi(argv[2]); buf_r = (char *)malloc(packet_size); if ((fd_r = open(argv[1], O_RDONLY)) < 0) { perror("open error : "); exit(0); } while(1) { while((n = read(fd_r, buf_r, packet_size)) > 0) { } } } |
fifo_client.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { int fd_w, fd_r; char buf_r[71]; int n, i; int packet_size; char *packet; int total_size = 100000000; int loop_size; packet_size = atoi(argv[2]); loop_size = total_size / packet_size; packet = (char *)malloc(packet_size); if ((fd_w = open(argv[1], O_WRONLY)) < 0) { perror("open error : "); exit(0); } memset(packet, '0', packet_size); packet[packet_size-1] = '\n'; loop_size = total_size / packet_size; for(i = 0; i < loop_size; i++) { write(fd_w, packet, packet_size); } printf("size %d %d\n", loop_size, loop_size * packet_size); } |
[root@localhost src]# ./fifo_server /tmp/fifo_server 512 ... [root@localhost src]# time ./fifo_client_mem /tmp/fifo_server 512 size 195312 99999744 real 0m1.433s user 0m0.060s sys 0m0.960s |
2.5.3절. Message Queue
message_queue_server.c
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h> struct msgbuf { long msgtype; char buf[512]; }; int main(int argc, char **argv) { key_t key_id; int i; struct msgbuf mybuf; int msgtype; key_id = msgget((key_t)1234, IPC_CREAT|0666); if(key_id == -1) { perror("msgget error : "); exit(0); } while(1) { if (msgrcv(key_id, (void *)&mybuf, sizeof(struct msgbuf), 1, 0) == -1) { perror("quit : "); exit(0); } } } |
message_queue_client.c
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/stat.h> struct msgbuf { long msgtype; char buf[512]; }; int main(int argc, char **argv) { key_t key_id; int i, n; struct msgbuf mybuf; int fd_r; int packet_size; int total_size = 100000000; int loop_size; packet_size = 512; loop_size = total_size / packet_size; key_id = msgget((key_t)1234, IPC_CREAT|0666); if(key_id == -1) { perror("msgget error : "); exit(0); } mybuf.msgtype = 1; memset(mybuf.buf, '1', 511); mybuf.buf[511] = 0x00; for(i = 0; i < loop_size; i++) { if (msgsnd(key_id, (void *)&mybuf, sizeof(struct msgbuf), 0) == -1) { perror("msgsnd error : "); exit(0); } } exit(0); } |
2.6절. 테스트 결과
다음은 테스트 결과 이다. 테스트횟수는 위에서 언급했듯이 5번씩 이루어졌으며, 패킷 사이즈에 따라서(71 바이트, 512 바이트) 2번에 걸쳐 테스트를 했다.
2.6.1절. 테스트 결과 1 (71 바이트)
다음은 테스트 결과를 정리한 데이타 파일이다. 파일이름은 data_71.dat 로 정했다.
# real time user time sys time #domain queue shared domain queue shared domain queue shared #============================================================== 1 4.603 2.929 2.675 0.350 0.350 0.250 2.240 1.070 1.110 2 4.782 2.863 2.712 0.450 0.430 0.330 2.350 0.970 1.070 3 4.859 2.956 2.734 0.400 0.390 0.270 2.450 1.010 1.080 4 4.516 3.069 2.757 0.390 0.480 0.280 1.980 0.980 1.150 5 4.544 2.935 2.818 2.120 0.390 0.290 2.120 1.080 1.260 |
위의 data 파일을 gnuplot 으로 보기 위해서 다음과 같은 gnuplot 작업파일을 만들었다. 파일이름은 real_71.dem 으로 했다.
set yrange[2.5:5] set xrange [1:5] set xtics 0,1,5 set title "real time" plot 'data_71.dat' using 1:2 t "queue" with l, 'data_71.dat' using 1:3 t "fifo" with l, 'data_71.dat' using 1:4 t "domain socket" with l pause -1 "Hit return to continue" set title "user time" set yrange[0.2:0.5] plot 'data_71.dat' using 1:5 t "queue" with l, 'data_71.dat' using 1:6 t "fifo" with l, 'data_71.dat' using 1:7 t "domain socket" with l pause -1 "Hit return to continue" set title "sys time" set yrange[1:3] plot 'data_71.dat' using 1:8 t "queue" with l, 'data_71.dat' using 1:9 t "fifo" with l, 'data_71.dat' using 1:10 t "domain socket" with l pause -1 "Hit return to continue" |
[root@loalhost src]# gnuplot real_71.dem Hit return to continue |
그림 1. real_time_71
그림 2. user_time_71
그림 3. sys_time_71
2.6.2절. 테스트 결과 2 (512 바이트)
다음은 테스트 결과를 정리한 파일이다. 파일이름은 data_512.dat 로 했다.
# real time user time sys time #domain queue shared domain queue shared domain queue shared #============================================================== 1 0.739 0.509 0.547 0.030 0.030 0.070 0.320 0.170 0.180 2 0.790 0.414 0.486 0.040 0.050 0.050 0.390 0.150 0.190 3 0.744 0.442 0.545 0.020 0.070 0.010 0.280 0.160 0.330 4 0.753 0.454 0.521 0.040 0.030 0.040 0.300 0.190 0.220 5 0.757 0.405 0.485 0.050 0.070 0.030 0.310 0.150 0.240 |
위의 data 파일을 gnuplot 으로 보기 위해서 다음과 같은 gnuplot 작업파일을 만들었다. 파일이름은 real_512.dem 으로 했다.
set yrange[0.4:0.9] set xrange [1:5] set xtics 0,1,5 set title "real time" plot 'data_512.dat' using 1:2 t "queue" with l, 'data_512.dat' using 1:3 t "fifo " with l, 'data_512.dat' using 1:4 t "domain socket" with l pause -1 "Hit return to continue" set title "user time" set yrange[0:0.10] plot 'data_512.dat' using 1:5 t "queue" with l, 'data_512.dat' using 1:6 t "fifo " with l, 'data_512.dat' using 1:7 t "domain socket" with l pause -1 "Hit return to continue" set title "sys time" set yrange[0.1:0.4] plot 'data_512.dat' using 1:8 t "queue" with l, 'data_512.dat' using 1:9 t "fifo " with l, 'data_512.dat' using 1:10 t "domain socket" with l pause -1 "Hit return to continue" |
그림 4. real_time_512
그림 5. user_time_512
그림 6. sys_time_512
2.7절. IPC 성능 테스트결과에 대한 분석
테스트결과는 real time 을 가지고 분석하도록 하겠다. 그래프를 보면 알겠지만 모든 경우에 있어서 Unix Domain Socket 가 확실히 다른 것들보다 느림을 알수 있다. fifo 와 message queue 의 경우에는 비슷하게 빠른 속도를 보여주고 있다.
2.8절. 무엇을 선택하는게 좋을까?
확실히 Unix Domain Socket 가 다른것들 보다 느리긴 하지만, 느림이 문제가 되는경우는 없을것 같다. 계산을 해보면 알겠지만.. 71 바이트씩 메시지를 전송 했을경우 초당 28만건의 메시지 전송이 가능하며 512 바이트식 메시지를 전송했을경우 초당 약 24만건의 메시지 전송이 가능하다라는 계산이 나온다. 즉 어떤 IPC 를 사용하더라도 속도때문에 문제가 되는경우는 거의 없다고 봐도 무관하다라고 볼수 있다.
그렇다면 좀더 프로그래밍 하기 쉽고, 안정적이고 확장성이 용이한 쪽에 중심을 두고 IPC 설비를 선택해야 할것이다.
그런측면에서 본다면 FIFO 와 message queue 는 제어하기가 까다롭다는 문제점을 가지며, 확장이 용이하지 않다는 단점을 가진다. 에러처리도 그리 수월하지 않다. 반면 Unix 도메인 소켓은 확장이 용이하며 제어하기가 비교적 쉬우며, 에러처리 역시 수월하다는 장점을 가진다.
결론은 아주 간단한 경우가 아니면 Unix Domain Socket 를 사용하는게 유리할거라는게 필자의 견해이다.