본문 바로가기
공부하는 하스씨/Graphics

Frame Buffer 이야기 (6)

by 박하스. 2008. 11. 26.
728x90
반응형

출처: KELP 이규명님의 강좌 http://kelp.or.kr/korweblog/stories.php?story=02/11/09/8557035&topic=29

== 시작하기에 앞서
이 글에 있는 모든 내용은 글쓴이가 가지고 있는 ATI mach 64(2MB) 그래픽 카드가 달려있고 RedHat 8.0이 설치된 PC(또는 Permedia2(8MB) 그래픽 카드가 달려 있는 RedHat 7.3이 설치된 PC)에서 제대로 동작하지만 이 글에 있는 모든 내용이 정확하다고 말씀 드릴 수 없습니다. 이 글에 있는 내용을 따라 했을 때 혹 생길 지 모르는 모든 문제에 대해서 글쓴이는 책임을 지지 않습니다. 글의 내용 중에 잘못된 내용이 있거나 질문할 것이 있으면 위쪽의 “holelee”를 누르고 메일을 보내기 바랍니다.

== 시작
lseek/write를 이용하는 pixel 찍기는 우선 코딩상 귀찮습니다. lseek, write 시스템 호출이 에러를 리턴하는지 체크해야 하고 그에 따른 error 처리 루틴도 만들어야 하죠.(물론 mmap이 리턴한 address로 pixel을 찍거나 읽을 때도 잘못하면 에러가 발생할 수 있습니다. 그러나 에러가 Segmentation Fault나 Bus Error로 프로그램이 그냥 죽을 겁니다.) 그러나 그런 것만 가지고는 “다소 무식한(?)”이라는 표현이 조금 과한 표현입니다. 이제 왜 “다소 무식한(?)”지 알아보기 위해서 벤치마크를 해보도록 하겠습니다.

== 벤치마크 시스템
벤치마크를 담당할 시스템은 본인이 집에 가지고 있는 linux machine입니다. 컴퓨터 사양은 다음과 같습니다.
OS : RedHat 8.0(default로 깔리는 kernel)
CPU : PentiumPro 200Mhz
Graphic Card : ATI mach 64 CT 2MB
Frame buffer driver : atyfb

== 벤치마크 프로그램 : 다시 나온 random 네모 그리기
random 네모 그리기를 mmap을 이용하여 다시 작성했습니다. 이번에 작성된 프로그램을 fbrandrect2라고 부르고 전에 lseek/write를 이용하여 작성된 프로그램을 fbrandrect1이라고 부르겠습니다. 소스의 골격이나 구성은 fbrandrect1과 fbrandrect2 모두 동일합니다. 다만 무한 루프를 돌면 곤란하므로 두 프로그램 모두 그냥 1000개의 네모만 그리고 프로그램이 종료되도록 하였습니다. 그리고 rand 함수가 똑 같은 값을 리턴해야 두 프로그램의 동작이 똑같아 지므로 srand 함수를 이용하여 rand 함수의 seed도 주었습니다.
===============================
/*
* fbrandrect2.c : Frame buffer draw random rectangular example unsing mmap
*
* Copyright(C) 2002 holelee
*
*/

#include <stdio.h>
#include <stdlib.h> /* for exit */
#include <unistd.h> /* for open/close .. */
#include <fcntl.h> /* for O_RDONLY */
#include <sys/ioctl.h> /* for ioctl */
#include <sys/mman.h>
#include <linux/fb.h> /* for fb_var_screeninfo, FBIOGET_VSCREENINFO */

#define FBDEVFILE "/dev/fb"

int main()
{
int fbfd;
int ret;
struct fb_var_screeninfo fbvar;
unsigned short *pfbdata;
int count = 1000;

fbfd = open(FBDEVFILE, O_RDWR);
if(fbfd < 0)
{
perror("fbdev open");
exit(1);
}

ret = ioctl(fbfd, FBIOGET_VSCREENINFO, &fbvar);
if(ret < 0)
{
perror("fbdev ioctl");
exit(1);
}

if(fbvar.bits_per_pixel != 16)
{
fprintf(stderr, "bpp is not 16\n");
exit(1);
}

pfbdata = (unsigned short *)
mmap(0, fbvar.xres*fbvar.yres*16/8,
PROT_READ|PROT_WRITE, MAP_SHARED, fbfd, 0);

if((unsigned)pfbdata == (unsigned)-1)
{
perror("fbdev mmap");
exit(1);
}

srand(1); /* seed for rand */
while(0 < count--)
{
int xpos1, ypos1;
int xpos2, ypos2;
int offset;
int rpixel;
int t, tt;

/* random number between 0 and xres */
xpos1 = (int)((fbvar.xres*1.0*rand())/(RAND_MAX+1.0));
xpos2 = (int)((fbvar.xres*1.0*rand())/(RAND_MAX+1.0));

/* random number between 0 and yres */
ypos1 = (int)((fbvar.yres*1.0*rand())/(RAND_MAX+1.0));
ypos2 = (int)((fbvar.yres*1.0*rand())/(RAND_MAX+1.0));

if(xpos1 > xpos2)
{
t = xpos1;
xpos1 = xpos2;
xpos2 = t;
}

if(ypos1 > ypos2)
{
t = ypos1;
ypos1 = ypos2;
ypos2 = t;
}

/* random number between 0 and 65535(2^16-1) */
rpixel = (int)(65536.0*rand()/(RAND_MAX+1.0));

for(t = ypos1; t <= ypos2; t++)
{
offset = t*fbvar.xres;

for(tt = xpos1; tt <= xpos2; tt++)
*(pfbdata+offset+tt) = rpixel;;
}
}

munmap(pfbdata, fbvar.xres*fbvar.yres*16/8);
close(fbfd);
exit(0);
return 0;
}

===============================
소스는 굳이 설명을 하지 않겠습니다.

== 벤치 마크 결과
fbrandrect1, fbrandrect2 모두 gcc의 “–O2” 옵션을 주고 컴파일 하였고 time 명령어로 수행되는 시간을 측정해 보았습니다.

fbrandrect2
real 0m4.736s
user 0m4.730s
sys 0m0.006s
fbrandrect1
real 3m53.784s
user 0m34.213s
sys 3m19.572s

어떻습니까? 차이가 보입니까? fbrandrect2는 5초도 되지 않은 시간에 끝이 났고, fbrandrect1은 4분 가까이나 걸렸네요. 눈으로 네모가 그려지는 모습을 보더라도 확연히 성능 차이를 확인할 수 있습니다. 이제 왜 무식한 방법이라고 얘기했는지 이해할 수 있겠죠?

== fbrandrect1은 왜 성능이 떨어지는가?
Frame buffer 이야기와는 크게 상관은 없지만 fbrandrect1이 성능이 떨어지는 이유에 대해서 짤막하게 살펴 보겠습니다. 그 이유는 lseek/write 시스템 호출을 많이 했기 때문입니다. 시스템 호출(system call)은 software interrupt라는 instruction을 수행하게 되어 있습니다.(instruction 이름은 CPU architecture마다 다르게 부르고 instruction 자체도 CPU architecture마다 다르죠.) 그 instruction이 수행되면 CPU는 interrupt를 받은 경우와 동일하게 동작합니다. Interrupt를 받으면 우선 CPU안에 있는 레지스터를 지정된 위치에 저장하고 어떤 이유로 interrupt가 걸렸는지 살펴보고 그것에 따라서 kernel 내부에서 그 interrupt를 서비스하는 함수를 호출합니다. write 시스템 호출을 했다면 linux에서 서비스 함수는 sys_write일 겁니다. 이 sys_write는 file descriptor를 살펴서 그 파일이 frame buffer device인지 판단하고 다시 그 frame buffer driver의 write함수를 호출하게 됩니다. 그 함수의 수행이 끝나게 되면 schedule을 다시 해야 할 필요가 있는지 계산해보고 그렇지 않다면 다시 저장된 레지스터를 복원해 놓고 user application으로 리턴합니다. User application으로 돌아오게 되면 CPU의 cache가 비어있으므로 cache miss가 발생하여 DRAM으로부터 cache의 내용을 다시 채우게 됩니다. fbrandrect1이 한 pixel을 찍을 때마다 write 시스템 호출을 하므로 이 모든 일을 반복하게 됩니다. lseek 시스템 호출도 마찬가지죠. 따라서 성능은 엄청 떨어지죠.

== 마치며
다음 글의 내용은 아직 미정입니다. 다음 글을 적을 때까지는 시간이 좀 걸릴 것 같습니다.

 

728x90
반응형

'공부하는 하스씨 > Graphics' 카테고리의 다른 글

Frame Buffer 이야기 (8)  (0) 2008.11.26
Frame Buffer 이야기 (7)  (0) 2008.11.26
Frame Buffer 이야기 (5)  (0) 2008.11.26
Frame Buffer 이야기 (4)  (0) 2008.11.26
Frame Buffer 이야기 (3)  (0) 2008.11.26