본문 바로가기

Miscellaneous/Design Pattern

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


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

아이폰/아이팟 터치는 ARM코어를 기반으로 한 CPU를 탑재하고 있다.

개발자 입장에서 볼 때 ARM의 가장 큰 특징은 RISC 방식이라는 것이다.

컴퓨터는 CPU가 CISC 방식일 때 보급되기 시작하였고, 이에 따라 현재까지도 대부분의 컴퓨터 서적들이 RISC CPU의 특성을 전혀 고려 하지 않은 예제들만 제시한다.

간단한 예제를 통하여 이러한 "일반적인" 프로그래밍 방식의 문제점들을 짚어보도록 하겠다.

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

다음과 같은 이미지 데이터 타입이 있다.,

typedef struct {
 unsigned short height;
 unsigned short width;
 unsigned short *imgBuf;
} img_type;

구조체 안에 이미지의 너비, 높이, 그리고 실질적인 이미지 버퍼를 가리키는 포인터가 존재한다.
이미지 버퍼는 휴대폰에서 많이 사용하는 픽셀당 16비트인 RGB565 버퍼이다.

너비와 높이도 각각 16비트만을 차지하는데, 16비트의 최대값인 65535이면 상당히 넉넉하다.

데이터 타입 자체는 아무런 문제가 없다 할 수 있겠다.

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

그러면 이 이미지를 백슬래시 축으로 뒤집어 엎는 함수를 작성 해 보겠다.

void FlipImage(img_type *pDst, img-type *pSrc)
{
  unsigned short x,y;

  pDst->width = pSrc->height;
  pDst->height = pSrc->width;

  for(y = 0; y < pSrc->height; ++y)
  {
    for(x = 0; x < pSrc->width; ++x)
    {
      pDst->imgBuf[x* pSrc->height + y] = pSrc->imgBuf[x + y * pSrc->width];
     }
  }
}

당연한 얘기지만 Src의 높이는 Dst의 너비가 되고, 너비는 높이가 된다.

루푸 내부의 한 줄이 수학적으로 다소 복잡하게 느껴질지 모르지만 사실은 상당히 간단한 함수이다.

코드도 짧고 상당히 깔끔하다.
프로그래밍 언어 책에서 예제로 제시할만한 함수이면서, 이 코드를 직접 작성한 사람 또한 단순명료함에 스스로 흡족해 할 것이다.

하지만 퍼포먼스 관점에서 상기 예제는 0점짜리이다.

그럼 지금부터 단계별로 이 예제의 약점들을 하나 하나 짚어보도록 하겠다.

모든 약점들을 보완한다면 상기 함수는 2배이상 빨라질 것이다.

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

바로 첫 줄부터 큰 문제가 있다.

unsigned short x, y;

단순한 지역변수 정의인데 무엇이 문제란 말인가...????

ARM같은 RISC 코어는 (지역)변수로 32비트를 극도로 선호한다.

그 이유는 ARM은 오로지 32비트로만 연산을 수행해야하기 때문이다.
(멀티미디어 특수확장 명령어 제외)

CPU내의 연산을 맡는 부분을 ALU라 하는데, ARM의 ALU는 오로지 32비트로만 연산을 처리한다.

하단의 for 루프튼 (내부적으로) 끝부분에서 x나 y값에 1을 더하고 결과물이 각각의 루프 종료 조건에 부합되는지 체크한다.

그러나 x나 y가 16비트 데이터 타입으로 지정되어 있기에 덧셈 후 또 하나의 연산을 수행하게 된다.

16비트 unsigned의 표현 범위는 0~65535이다. 그리고 16비트 변수가 65535인 상태에서 1을 더하게 되면 65536이 아닌 0이 되어야만 한다. 바로 이것을 달성하기위한 로지컬 연산이 추가 된다.

어셈블리에서는
bic x, x, #0x00010000
이라는 명령어가 추가되는데, bic는 "Bit Clear"라는 뜻으로 맨 마지막 값(0x00010000)에서 1로 마크된 비트를 두번재 값(x)에서 0으로 클리어하고 첫번째 값(x)에 써 주는 것이다.

이 한 개의 명령어가 일단 별거 아닌 듯 하지만 루프 내부에 있다는 것이 문제이다.
다 합하면 (width*height + height) 만큼 수행이 된다.

VGA 해상도의 이미지라면 정확히 307689회 불필요한 연산이 수행되는 것입니다.

상기 함수가 초당 30회 호출된다면 9230400 싸이클이 낭비되는 것인데, 이만해도 9메가헤르츠 CPU클럭이다.

해법은 아주 간단하다. unsigned short을 unsigned int로 바꿔주면 해결된다.

unsigned int x, y;











반응형