본문 바로가기

Miscellaneous/Design Pattern

퍼포먼스 최적화의 기본 (2편)

[본 글은 Naver 맥부기 Cafe의 리겔님의 글을 정리한 것입니다.]


void FlipImage(img_type * pDst, img_type * pSrc)
{
  unsigned int x, y;
 
  pDst->width = pSrc->height;
  pDst->height = pSrc->width;
 
  for (y = 0; y < pDst->height; ++y)
  {
    for (x = 0; x < pDst->width; ++x)
    {
       pDst->imgBuf[x*pSrc->height + y] = pSrc->imgBuf[x + y*pSrc->width];
    }
  }
}


==========================================================================

위 함수가 받는 인자는 두 개 이다. 그리고 두 개 다 구조체의 포인터이다.

그리고 구조체는 메모리상에 존재하고, 다라서 구조체의 멤버를 읽어오는 것은 메모리 억세스에 해당이 된다.

CPU에는 레지스터라는게 존재한다.

이것은 그 무엇보다도 빠른 코어 내장 메모리로서, RISC 구조에서 모든 연산은 레지스터에서만 수행 할 수 있다.

즉, 전역변수나 구조체 등 메모리상에 존재하는 변수의 값을 바꾸려면
1. 레지스터로 읽어온다.
2. 해당 레지스터 값을 변경 해 준다.
3. 레지스터 값을 해당 번지수 메모리에 써 준다.

이런 3단계의 과정을 거치게 된다.

예제함수에서는 루프 내부에서 구조체의 값을 변경하는 파트가 없으로 3단계는 생략되지만,
일단 그 값을 레지스터로 읽어들인 후에 사용을 하는 과정을 매번 반복하게 된다.

구조체 내부의 값이 바뀔리가 없는 상황이지만 컴파일러는 멀티태스킹 환경에서 다른 태스크가 해당 메모리 값을 바궈버리는 경우까지 감안해야 하므로 매번 다시 읽어오고, 이를 비교 대상으로 사용한다. (x, y 루프)

이를 해결하는 방법 또한 상당히 간단하다. 바로 지역변수를 활용하는 것이다.

지역변수는 말 그대로 지역변수이기때문에 대부분의 경우 레지스터에 1대1에 할당된다.

void FlipImage(img_type * pDst, img_type * pSrc)
{
  unsigned int x, y;
  unsigned int srcwidth, srcheight;

  unsigned short * pDstBuf, * pSrcBuf;

 

  srcwidth = pSrc->width;
  srcheight = pSrc->height;

  pDstBuf = pDst->imgBuf;

  pSrcBuf = pSrc->imgBuf;


  pDst->width = srcheight;
  pDst->height = srcwidth;
 
  for (y = 0; y < srcheight; ++y)
  {
    for (x = 0; x < srcwidth; ++x)
    {
       pDstBuf[x*srcheight + y] = pSrcBuf[x + y*srcwidth];
    }
  }
}

코드 전체가 다소 길어졌지만 지역변수를 활용하여 루프 내부에서 구조체 참조를 말끔히 없앨 수 있었다. 가독성 또한 오히려 향상되었다.

그리고 실제로 컴파일 해 보면 변경 이전 보다 작은 바이너리가 나온다.

함수를 짧게 짜는게 능사가 아니라고 생각한다. 오히려 수학 공식을 풀어나가듯 단계별로 길게 나열하는 편이 가독성에 유리하고, 디버깅 또한 용이하다.

변경된 함수는 이미 이전 버전보다 2배 이상의 속도로 실행된다.
루푸 내부에서 불필요한 메모리 참조를 전부 제거하였기 때문이다.



반응형