Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

최적화

10. 최적화

최적화란 여러분이 만든 애플리케이션을 좀더 효과적으로 실행되게끔 만들기 위한 일련의 과정들을 말한다. 여러분은 속도, 메모리 사용공간, 디스크 사용공간등 많은 부분에 있어서 좀 더 효율적인 사용이 가능하도록 최적화 시킬 수 있다. 이번 장에서는 속도에 중점을 둔 최적화 방안에 대해서 토론해 보도록 하겠다.

10.1. 최적화가 필요한 시점

모든 경우에 반드시 최적화가 필요한 것은 아니다. 왜냐하면 더 나은 최적화는 그만큼 복잡한 과정을 필요로 하기 때문이다. 이러한 복잡한 과정은 필연적으로 코드의 양을 늘리고, 코드를 이해하는데 전문성을 필요하도록 만든다. 높은수준으로 최적화된 코드는 보통, 이해하고 디버깅하기 힘들어 질 수 있다. 결국 프로젝트의 완료시간과 유지보수 시간을 증가시키는 요인이 된다. 그러므로 꼭 필요한 부분에 대해서 최적화를 해주어야 한다.

그러나 프로그램의 어느부분에서 속력을 저하시키는 병목현상이 발생할지를 예측하는 것은 경험 많은 프로그래머라고 할지라도 예측하기가 쉽지 않다. 심지어 프로그래머를 직접 실행 시켜보더라도, 병목현상이 발생하는 코드의 위치를 찾아낸다는 것은 결코 쉬운일이 아니다. 일반적으로 프로그램의 속도가 저하되는 원인은 병목구간의 코드에 의한바가 많으므로, 병목을 일으키는 코드를 찾아내는 것은 매우 중요한일이며, 실제 속도관련 최적화는 병목구간을 찾아내는 일이 가장 중요하다.

당신이 프로그램을 개발할때, 당신은 다음의 원칙에 따라서 프로그램을 개발하도록 노력해야 한다.

  • 모든 것을 문서화 한다.

  • 작업의 과정역시 문서화 한다.

  • 코드는 모듈화시키고, 쉽게 보고, 이해할 수 있도록 작성한다.

문서화가 기본중의 기본이라는 것은 말할필요도 없으며, 특히나 그룹단위의 개발을 해야한다면, 그 중요성은 배가 된다. 프로그램의 함수화 역시 기본이다. 이쯤되면 위의 목록의 내용들이 중요한건 알겠는데, 애플리케이션의 속도와 무슨 관련이 있지 ? 하고 의아해 할 수 있을 것이다. 일단 최적화는 초기 개발단계에서는 다음과 같은 이유 때문에 그리 필요하지 않은 작업이다.

  • 약간의 속도의 문제는 더 좋은 하드웨어로 해결할 수 있다. 종종 더 좋은 하드웨어를 구입하는게, 좋은 프로그래머를 고용하는 것 보다 저렴하다.

  • 처음 만들어진 프로그램은 요구사항의 변경, 새로운 기술 도입, 알고리즘과 프로세스 변경, 프로그래머의 능력향상 등의 이유로 80%정도 다시 만들어 진다. 즉 처음 구상한것과는 전혀 다른 프로그램이 될 가능성이 많다. 변경될 가능성이 많은 코드를 그때 그때 최적화 하는건 시간낭비다.

  • 속도문제는 코드의 단지 몇군데 지역적인 곳에서 발생한다. 이러한 문제를? 프로그램을 완전히 만들어서 테스트하기 전에 찾아낸다는 건 매우 어려운 일이다.

나는 이전에 웹기반의 전자상거래 프로젝트에 투입된 일이 있었으며, 정확하고 깔끔 하게 작동하는데 프로젝트의 촛점을 맞추고 작업을 진행 했었다. 당시 같이 일하던 동료는 웹 페이지가 로딩된 후 일을 마치고 종료될때까지 무려 12초나 걸린다는 걸로 무척이나 고민을 하고 있었다(모든 웹페이지는 1초안에 필요한 작업을 마치고 결과를 출력해야 했었다). 그때 나는 속도에 신경쓰지 말고 정확히 작동하도록 만드는데 신경쓰자고 했으며, 최적화는 가장 나중 우선순위로 하자고 권고하고, 그렇게 작업을 하도록 했다. 마침내 3달에 걸쳐서 완벽하게 작동되는 코드를 만들었고, 3일에 거쳐 병목현상이 일어나는 부분을 찾아서 속도문제를 해결했다. 정확히 작동하도록 하는 기능구현에 촛점을 맞춤으로써, 정확성과 성능 모두를 만족시키는 프로젝트를 수행하게 되었다.

10.2. 어디를 최적화 할 것인가

최적화해야 될 부분은 많다. 그러나 시간과 능력은 한정되어 있으므로, 최적화를 통해서 많은 이득을 얻을 수 있는 부분을 선별해내야 한다. 최적화할 부분을 찾아내는 가장 좋은 방법은 profiler를 이용하는 방법이다. profiler는 각 함수별로 얼마만큼의 시간을 소비하는지 통계를 내는 프로그램이다. GNU/Linux 환경에서는 grpof라는 표준 profile 프로그램을 제공한다. 프로그래머는 profiler의 결과를 보고, 어느 함수가 가장 많은 시간을 소비했는지를 확인해서, 함수단위로 최적화 시켜야할 부분을 결정할 수 있게 된다. profiler의 사용방법은 이 문서에서 논외로 하겠다. profiler에 대한 자세한 내용은 gprofile 사용문서를 참고하기 바란다.

profile결과 특정함수가 단지 1%정도의 시간만을 소비하고 있다면, 이 함수를 최적화할 필요는 없다. 그러나 만약 20%정도의 시간을 소비하는 함수가 있다면, 이 함수에 대해서는 주목할 필요가 있다. 소비하는 정도가 큰 함수일 수록 약간의 최적화 만으로 큰 효과를 얻을 수 있기 때문이다. 6시간의 노력으로 1%의 함수를 0.9%로 최적화 하는 것과 20%의 함수를 5%최적화하는 것, 어느게 효율적인지는 말할 필요도 없다.

최적화에는 지역최적화와 전역최적화 두개의 커다란 영역이 있다. 지역최적화는 주로 하드웨어 특성과 프로그램 특성에 관련된 것들이다. 전역최적화는 프로그램의 구조적인 것들과 관계있다. 예를 들어 지하철 노선에서 각 역간 최단 거리를 확보하기 위한 노선을?찾아주는 함수가 있다면, 다양한 알고리즘을 코드에 적용함으로써 빠른시간안에 최단거리를 찾아주는 함수의 작성이 가능할 것이다.

10.3. 지역 최적화 방안

여기에서는 지역 최적화를 위해서 알려진 방법중 몇 가지를 소개하도록 하겠다. 고수준 언어를 사용할 경우에는 보통 컴파일러의 최적화 관련 옵션을 이용함으로써 자동적으로 최적화된 코드를 만들어 낼 수 있다.

미리계산된 값을 넘겨라

함수는 일반적으로 입력과 출력이 가능한 데이터갯수에 제한을 가지고 있다. 그러므로 가능한한 어떤 함수를 호출하기 전에, 필요한 계산을 모두 한다음 최소한의 간단한 정보만을 함수에게 넘겨줄 필요가 있다. 함수는 가능한한 가볍게 만들어야 한다. 쓸데없이 다량의 데이터를 넘겨서, 함수가 많은일을 처리하도록 하는 커다란 함수의 생성은 지양하도록 한다.

결과 값을 기억하라

함수를 호출해서 어떤 계산을 수행하는 데에는 많은 비용이 소비된다. 그러므로 가능한 함수 호출을 줄여야할 필요가 있다. 자주 사용될 수 있는 계산된 데이터는 메모리에 저장한 후, 다른 필요한 곳에서 재사용하도록 하면 함수 호출을 줄일 수 있다. 예를 들어 현재 시간을 가지고 계산을 하는 함수가 3개 정도 있다고 가정해 보자. 이경우 매번 현재 시간을 가져오는 time()함수를 호출하는 것은 낭비다. 한번 time()함수를 호출하고, 그 값을 메모리에 저장한다음, 필요한 함수가 사용할 수 있도록 하는 것이 훨씬 효율적이다. 이러한 작업은 때때로 caching 혹은 memoizing이라고 불리운다.

register의 이용

Register는 컴퓨터에서 빠르게 접근할 수 있는 메모리 영역이다. 당신이 메모리에 접근하려고 한다면, 프로세스는 물리적으로 떨어져 있는 메모리에 접근하기 위해서 bus를 이용해서 메모리에 있는 데이터를 가져온다. 반면 register는 프로세스 그 자신이 가지고 있는 메모리 영역이다. 당연히 매우 빠를 수 밖에 없다. 그러므로 허용가능한 데이터를 레지스터에 올려놓고 사용을 할 필요가 있다. 고수준언어에서는 굳이 레지스터에 대해서 신경쓸필요 없이 알아서 레지스터를 쓸것인지를 결정한다.

inline 함수

함수는 프로그램의 관리를 위한 가장 중요한 요수중 하나다. 함수를 이용하면 프로그램을 모듈화 시킬 수 있으며, 재사용 가능하도록 만들 수 있다. 또한 쉽게 보고 이해할 수 있도록 만들어 준다. 그러나 함수를 호출하기 위해서는 stack영역으로 인자를 밀어넣고, jump해야 하는 오버헤드가 발생한다. 이러한 문제를 해결방안으로 많은 언어들이 inline 함수 혹은 inline 메크로 함수를 지원한다. inline함수를 사용하게 되면, 컴파일 할때 함수의 코드가 해당 영역에 직접 삽입된다. 함수호출을 하지 않기 때문에, 빨라지는 효과를 누릴 수 있지만 반면 코드가 매번 삽입이 되므로, 프로그램의 덩치가 커지게 될 것이다. 또한 재귀함수등에는 사용할 수 없는 등의 제약을 가진다. 만약 순환문등에 inline함수를 사용한다면 특별히 많은 효과를 누릴 수 있을 것이다.

addressing modes

어드레싱 모드에는 몇가지 종류가 있다는 것을 알고 있을 것이다. 이들 어드레싱 모드는 각각 서로 다른 속도를 가지고 있다. immediate와 register 어드레싱 모드가 가장 빠르며, Direct, indirect 모드 순으로 빠르다. pointer와 indexed indirect 모드가 그중 가장 느리다. 그러므로 가능한 빠른 주소모드를 사용할 필요가 있다.

Data Alignment

어떤 프로세서들은 데이터를 엑세스할 때 word-aligned 한다. 보통 word단위로 나누어서 엑세스하게 되는데, non-aligned data방식보다 빠르게 엑세스가 가능하다. 그래서 구조체를 만들게 될때, 구조체의 크기가 word단위로 만들어지는 것을 확인할 수 있다. 아래의 c코드를 컴파일 한다음 결과를 확인해 보도록 하자.

#include <stdio.h>

struct data
{
    int a;
    char b[1];
};

int main()
{
    struct data mydata;
    printf("%d\n", sizeof(mydata));
}

지금까지 언급한 방법들은 여러가지 지역최적화 방법중 몇가지에 불과하다. 하지만 가장 중요한 요소는 읽기쉽고 관리하기 쉬운 코드를 만드는데 있다는 걸 기억해 두길 바란다.

10.4. 전역 최적화 방안

전역최적화는 두가지 목표를 가진다. 첫번째는 지역 최적화가 가능하도록 코드를 작성 하는 것이다. 예를 들어, 만들어진 코드가 복잡한 흐름을 가진 커다란 하나의 procedure로 구성되어 있다면, 느리게 작동할 것이다. 이것을 단순한 일을 하는 여러개의 코드로 쪼갠다음 계산된 값을 메모리에 저장하거나 다른 함수로 넘기는 식으로 해서 효율적으로 돌아가게끔 만들 수 있다.

stateless 함수(어떠한 글로벌함수나, 시스템콜도 사용하지 않고, 받아들인 인수를 가지고 단지 연산만 하는 함수)는 최적화를 위한 좋은 방법이다. 프로그래머는 가능하면 프로그램을 Stateless한 함수들로 모듈화 함으로써 최적화 정도를 높일 수 있다. 전자상거래 프로젝트를 예로 들어 보겠다. 전자상거래에서는 특정한 제품 목록과 관련된 정보를 가지고 오는데, 이를 위해서 12번의 데이터베이스 호출을 해야했고, 최악의 경우 20초라는 시간을 소비했다. 이 프로그램은 웹을 통해서 고객과 상호대화 해야 하는데, 이는 너무나 긴시간이였다. 이 문제를 해결하기 위해서 초기 프로그램이 시작할 때, 제품목록을 읽어와서 메모리에 적재하고, 함수에서는 메모리의 제품목록을 가져오도록 즉 statless한 함수를 만드는 방법을 사용했다. 제품의 목록은 변경이 될 수 있으므로, 일정시간 간격으로 메모리의 내용을 갱신하도록 하는 또다른 함수를 만들었으며 결과적으로 1초안에 작동하는 프로그램을 만들어냈다.

전역 최적화를 위해서 보통 아래와 같은 특성을 갖추도록 프로그램을 작성한다.

병렬화

병렬화는 코드가 다중 프로세스에 의해서 분할되어서 실행하는 것을 의미한다. 자동차를 만든다고 가정을 해보자. 이경우 엔진과 인테리어를 만드는 부분은 서로 독립시켜서 동시에 작업이 가능하도록 할 수 있다. 만약 여러분의 컴퓨터가 다중의 프로세스나 클러스터링 되어 있고, 이들 자원을 이용할 수 있다면, 병렬화된 코드는 효율에서 많은 잇점을 얻을 수 있을 것이다.

stateless

stateless 함수와 프로그램은 함수사이에 명확하게 정의된 데이터만을 주고받는다. 물론 모든 함수가 stateless할 수는 없다. 전자상거래 프로그램을 예로들어보자. 대부분의 함수가 메모리에 있는 상품정보를 주고 받는다고 하더라도, 최초에 하나의 함수는 DB에 접근해서 상품정보를 읽어오는 일을 해야할 것이다. 이 함수는 stateless할 수 없다. stateless하지 않은 함수의 실행을 최소화 시킬수는 있다. 상품정보라는게 변경이 자주 일어나는게 아니기 때문에, 하루에 한번만 DB에서 정보를 가져오는 등의 방법으로 이러한 함수의 실행을 최소화 시킬수 있으며, 실제 이러한 방법을 이용해서 프로그램을 최적화 시켰다.

10.5. 복습

  • 지역최적화와 전역최적화의 차이를 설명하라.

  • 지역최적화 방안의 몇가지 방법을 설명하라.