프로그램 최적화

From Hidden Wiki
Jump to navigation Jump to search

[목차]

개요

소프트웨어가 적은 자원으로 높은 효율을 낼 경우 최적화가 잘 되었다고 한다. 최적화된 프로그램들은 최적화되지 않은 프로그램들에 비해 메모리를 적게 사용하거나 실행 시간이 줄어드는 이점이 생긴다. 이 최적화 작업이 제대로 이루어지지 않는다면 많은 자원을 먹는데도 영 좋지 않은 효율을 보여 발적화나 개적화 등의 표현으로 불리며 까인다. 좁은 범위로는 기기의 성능을 업그레이드 시키는 펌웨어 업그레이드부터, 넓게는 윈도우즈 시리즈의 서비스팩 급과 같은 대규모 업데이트까지도 포함된다.

윈도우 임베디드 컴팩트를 연구하는 이들에게는 뼈저리게 다가오는 단어다. 임베디드 시스템 프로그래밍에서는 속도와 용량 사이의 상반관계(Trade-off)를 감안해야 하기 때문에 어느쪽을 우선시 하느냐에 따라 최적화의 방향이 달라질 수도 있다.

사용자 경험에 가장 크게 기여하는 개념이기도 하다. 대표적인 예로 조립컴퓨터가 있다. 아무리 고성능부품들만 써도 최적화가 잘 안되어있으면 오히려 성능이 떨어지는 모습을 보인다.

단, 어느 쪽이든 하드웨어를 바꾸는 업그레이드는 예외. 한정된 성능에서 최적의 모습을 보여주어야 하기 때문에 별 의미가 없다. 몇몇 회사들은 최적화에 한계가 있는 부분을 하드웨어를 업그레이드하여 커버하기도 한다. 이를 하드웨어로 최적화한다고 표현하기도 하지만 그런 개념은 없다.

최적화를 잘 하기로 유명한 기업에는 애플[* 소프트웨어와 하드웨어를 동시에 디자인하기 때문이다. 그래서 최적화에는 엄청난 강점을 보이는 기업이고 눈으로 보면 하드웨어는 아주 고스펙은 아닌데 거의 최대한의 퍼포먼스를 뽑아낼 때가 많다. 단, 애플에서 내놓은 윈도우 프로그램은 발적화도 모자라서 악성코드급인 경우도 있다. 자세한 것은 iTunesQuickTime Player 항목 참조.]과 --외계인을 갈아만든-- 심비안 버프의 노키아가 있다. 국내에서는 코원이 유명하다면 유명하다. 닌텐도의 경우 최적화를 잘 하기는 하나 닌텐도가 최적화를 잘 한다기보다는 닌텐도 게임기로 타이틀을 발매하는 서드파티가 자사의 게임에 발적화를 해서 닌텐도가 최적화되어 보이는 것 뿐. 실질적으로 개발사인 닌텐도만 하드웨어의 특징을 잘 살린다고 볼 수 있다.[* 닌텐도가 서드파티에게 개발시 필요한 최적화 기술을 알려주지 않아 그렇다는 말이 있다. 정확히 아는 분이 수정바람.]

NASA도 최적화로 유명하다. NASA는 (적어도 우주선에 붙일 것으로는) 절대로 최신 CPU를 구입하지 않고 오래된 구식 CPU[* 시중엔 도데카(12) 코어까지 나왔는데도 펜티엄급 프로세서를 사용한다. 최신 CPU일수록 성능은 좋은 대신 수명과 내구는 떨어지는 경향이 있다.]만을 구입한다. 신형일수록 민감도가 심해져 극한의 환경을 버틸 수 없기 때문에[* 회로 선폭이 좁아질수록 집적도와 속도가 향상되지만 물리적인 내구성은 당연히 취약해질 수 밖에 없다. 이런 이유로 선폭이 굵은 구식 회로 설계를 한 예전 CPU를 쓰는 것이다. 물론 상용 CPU를 그대로 쓰는 것이 아니라, 회로 구조를 유지한 채 웨이퍼의 재료로 실리콘 대신 튼튼한 사파이어([on Sapphire])를 사용하는 등 여러가지 방식으로 회로의 내구성을 향상시키는 마개조를 한다. 이런 처리를 거친([[1]]) 녀석들은 속도를 일부 희생하는 대신 통상 실리콘 소자 쯤은 순식간에 망가뜨리는 우주 배경 방사선을 맞아도 정상 작동하는 괴물같은 내구성을 자랑한다.] ~~질리도록~~ 오래도록 사용할 수 있는 안정적인 처리장치만을 요구한다고.[* 다만 이는 컴퓨팅파워가 남아돌고 CPU 수명이 극도로 짧아진 현대의 상황이며, 나사의 최적화로 가장 유명한 예시인 보이저호에는 적용되지 않는다. 보이저호가 발사된 것(1977년)은 인텔에서 8086을 발표(1978년)하기도 전의 일이라는 점을 고려하면 그 크기에 그 정도 스펙이면 당시로서는 상당히 훌륭한 수준이었다. 부족한 컴퓨팅 파워로 성능과 신뢰도를 얻어내기 위해 높은 수준의 최적화를 한 것은 맞지만.] 이에 대한 보다 자세한 이야기는 군사 & 우주용 CPU 문서도 참조할 것.

게임계에선 밸브 코퍼레이션이 최적화로 유명한 편이고, 크라이텍도 2011년부터 최적화로 유명해졌다. 크라이시스 2가 전작과는 달리 높은 그래픽에 비해 최적화가 잘 되어있기 때문. EVE 온라인의 경우 한 때 서버의 발적화로 욕을 많이 먹었다가 발적화를 싹 해결한 프로그래머가 (플레이어 중에 IT 업계 종사자가 많은 덕분에) 슈퍼스타 취급을 받기도 했다. 그리고 락스타 게임즈GTA 4는 심각한 개적화였지만 후속작인 GTA 5는 엄청난 신적화로 환호를 받았다. 또한 프로스트바이트 엔진을 사용한 게임들(배틀필드 4, 등)도 최적화가 잘 되어있는 편이다. 블리자드 엔터테인먼트 게임들의 경우 들쭉날쭉한 편. 와우, 디아블로 3와 오버워치는 어지간한 컴퓨터에서는 다 돌아가는 이른바 갓적화로 칭송받는다. 그러나 히어로즈 오브 더 스톰과 스타크래프트 2의 경우 엔진 자체의 문제 때문에 컴퓨터를 민감하게 타는 편이라 최적화가 좋다고 하기 어렵고, 하스스톤의 경우에도 PC와 모바일 모두 그다지 뛰어나지 않은 편.

EA DICE 역시 최적화에 있어 상당한 실력을 보여주고 있으며, 특히 스타워즈: 배틀프론트신적화라는 반응까지 나왔다. 배틀필드 1은 몇 가지 논란이 있기는 했지만 전체적으로 보면 훌륭한 최적화가 이루어졌다.

소프트웨어 최적화 전문가로서 가장 유명하고 장수하는 인물로는 마이크로소프트에서 존 카맥의 이드 소프트웨어로 스카우트되어 퀘이크 엔진을 최적화했던 전적의 마이클 압래쉬(Michael Abrash)가 있다. 현재까지도 최적화 최고수로 인정받고 있으며, 코드 한줄 한줄, 어셈블리어 하나 하나를 극한까지 쥐어짜내는 것으로 유명하다.

여담으로 사람의 뇌도 최적화 진행중이라는 연구결과가 있다. 뇌 크기는 점점 줄어들고 있는데, 지능은 오히려 높아지는 기현상이 발생하고 있기 때문.[[2]]

주 최적화 기법

* 알고리즘 개선
최적화의 기본 전제가 되는 것은 적합한 알고리즘이다. 알고리즘이 쓸데없는 낭비 없이 최적의 상태로 설계된 상태에서나 프로그램 코드 튜닝이 의미가 있지, 비효율적인 알고리즘을 쓰면서 온갖 최적화 기법을 동원해 봐야 전혀 의미가 없다. 극단적인 경우엔 1%의 성능을 높이기 위하여 99%의 시간을 쓰게 될 것이다.
* 병목 현상(bottleneck) 제거
대부분의 프로그램은 프로그램이 사용하는 모든 부분이 느리기 보다는, 조그마한 부분에서 50% 이상 느려지는 등의 병목이 발견되는 경우가 많다. 이러한 병목 현상을 줄이는 것은 적은 노력으로 엄청난 성능 향상을 가져올 수 있다.

마이크로 튜닝

최적의 알고리즘을 사용하고 주요 병목을 제거했는데도 시원치 않을 경우 군데군데를 손봐서 성능을 튜닝한다. 이런 최적화는 코드의 가독성이나 유지보수성을 등가교환해야 하는 경우가 많다. 이 트레이드오프를 감수하는 것은 프로그래머의 몫. 일반적으로 병목을 제거하는 것만으로도 충분히 빨라졌다면 튜닝을 굳이 할 필요는 없다.[* 충분히 빠르다면 튜닝하는게 오히려 해가 된다고 하는 조언이 있다.] 반면 그래도 좀더 나은 속도가 필요하다면 다음과 같은 튜닝을 시도하기도 한다.

* 구문을 인라인 어셈블리로 대체하여 컴파일러가 할 수 없는 수준의 최적화를 시행한다. 
그러나 2014년 현재 일반적인 환경에서 이런 작업을 필요로 하는 경우는 거의 없다. 컴파일러 최적화 기술의 발전에 따라 프로그래머에 준할 정도로 똑똑해졌기 때문이다. 더구나 컴파일러가 할 수 없는 수준의 최적화를 할 수 있는 프로그래머는 얼마되지 않는다. 정말 쥐어짜내더라도 컴파일러가 지원하는 인트릭 식으로 컴파일러의 최적화를 도와주거나 SIMD를 이용하는 정도가 한계.
* C언어를 작성할 때 변수 용량을 줄인다.
메모리 및 캐시 등의 자원이 매우 한정적일 때 사용하는 방법의 예. 예를 들어서 최대 10000이 들어갈 변수에 int형 대신 short int를 쓰는 식으로. 최근의 CPU는 사실상 속도 차이가 안나지만, 임베디드 환경처럼 제한된 자원만을 사용해서 최대한의 성능을 내야 하는 경우에는 사용될 수 있다.[* 단, 이는 양날의 검이 될 수 있는데 CPU는 데이터를 1Word 단위로 처리한다. 이 크기를 벗어나는 데이터를 처리하는 과정에서는 오버헤드가 발생하게 되는데, 이는 1Word 단위보다 작은 char, byte, short 등에도 적용된다. 즉, 메모리 효율성과 접근처리 시간 효율성 중에서 선택을 해야 하는 것이다. 예상되는 값의 크기가 낮다고 해서 무조건 작은 데이터형을 쓰는 것은 아니란 것. 하지만 작은 크기의 자료형을 쓰면 캐시에 더 많이 담을 수 있다는 장점도 있다. 결국은 프로그래머가 적절하게 골라서 써야 한다.]

컴파일러에서의 최적화

현재의 컴파일러들은 충분히 똑똑해져서, 적당한 설정을 해 주면 프로그래머를 대신하여 어느 정도 최적화를 해 준다.[* 대표적인 C 컴파일러인 GCC 컴파일러의 경우 -O1, -O2 등의 옵션을 통해 최적화된 코드를 작성할 수 있다.] 다만 최적화된 실행파일은 프로그래머가 만든 소스코드와 달라져[* 기능상의 차이점은 전혀 없지만, 흐름이나 순서 등에서 차이가 생길 수 있다. --기능상의 차이가 있다면 문제가 좀 있다--] 디버깅 난이도가 조금 올라간다는 단점이 있다.

그런데 컴파일러는 --아쉽게도-- 인간만큼 똑똑하지 않은지라 프로그램의 구문들을 인식할 수 있지만 프로그램의 흐름을 이해할 수는 없다. --이게 되면 프로그래머들 다 굶어 죽는다-- --야! 프로그래머들 굶어죽는 소리 좀 안 나개 하라!-- 컴파일러의 최적화는 최적화 테크닉을 적용 가능한 몇몇 구문들을 인식하면서 실행되는데, 요령있게 짜이지 않은 코드의 경우 컴파일러가 구문을 잘 인식할 수가 없어 자동으로 최적화가 되지 않는다. 이런 식으로 컴파일러의 자동 최적화를 막는 코드들을 '최적화 장애물(optimization blocker)'이라 부른다. 최적화 장애물을 치우는 것은 프로그래머들의 몫이다. 프로그래머들은 온갖 기술과 지식들을 동원해서 최적화 장애물을 치워 컴파일러가 최적화를 잘 할 수 있도록 도와야 한다.

컴파일러의 한계 예시

다음 코드를 보자. {{{#!syntax cpp void func1(int *xp, int *yp) { *xp += *yp; *xp += *yp; }

void func2(int *xp, int *yp) { *xp += 2 * (*yp); } }}}

데스크톱의 입장에서 func1와 func2는 동일한 기능을 하는 함수이다. 그러나, func1은 불필요하게 대입연산자와 덧셈 연산자를 한번 더 사용하므로, 컴파일러는 func1 과 같이 짜여진 코드를 최적화 과정에서 func2의 형태로 수정한다. 그러나 펌웨어나 임베디드 혹은 디바이스 드라이버 등을 작성을 할 때에는 시간에 따라 레지스터나 메모리의 값이 변화해야 하는 경우가 있다. (칩의 특정 핀에서 PWM 펄스를 출력한다거나..) 이런 경우에 func1을 func2와 같은 형태로 변화시킬 경우 개발자가 의도한 것과는 완전히 다른 동작을 하게 된다. 따라서 이와 같이 컴파일러가 최적화를 해선 안되는 경우에는 해당 변수에 'volatile'를 붙여 선언함으로써 컴파일러에 의한 최적화를 사전에 방지해야만 한다.

분류:프로그래밍