티스토리 뷰


편견이 깨지는 어셈블리 프로그래밍 : 최적화 강좌 3 - 3  
 
 





VC++의 최적화
VC ++나 델파이와 같은 고급 언어들의 컴파일 옵션에 보면 거의 포함되어 있는 메뉴가 최적화 메뉴다. 한때에는 고급 언어들의 성능 및 코드 생성 효율이 당시 하드웨어의 성능을 원활하게 끌어내기에는 부족했기 때문에 어셈블리어로 프로그램을 제작하기도 했었다. 하지만 현재는 높아진 하드웨어 성능과 많아진 메모리와 더불어 눈부시게 발전한 고급 컴파일러의 최적화 알고리즘에 의해 어셈블리어로 손수 최적화 코딩을 할 필요는 거의 없어졌다. 여기서는 지면관계상 고급 언어들을 다 다룰 수는 없고 이들 중 VC++를 선택해 최적화 기능의 예를 보여주려 한다.

<리스트 10> 간단한 테스트 예제
int TestOpt( int A_Test )
{

int Tv_Test1;

Tv_Test1 = A_Test + 2;

int Tv_Test2;

Tv_Test2 = Tv_Test2 * A_Test;

return Tv_Test2;

}

<리스트 10>의 소스를 최적화 옵션을 사용하지 않은 상태와 사용한 상태 두 가지로 컴파일해 디스어셈블해 보았다.

<리스트 11> <리스트 10>의 최적화 사용 안한 상태
00402FD0 push ebp
00402FD1 mov ebp,esp
00402FD3 sub esp,48h

107:
108: int Tv_Test1;
109:
110: Tv_Test1 = A_Test + 2;
00402FE8 mov eax,dword ptr [ebp+8]
00402FEB add eax,2
00402FEE mov dword ptr [ebp-4],eax
111:
112: int Tv_Test2;
113:
114: Tv_Test2 = Tv_Test2 * A_Test;
00402FF1 mov ecx,dword ptr [ebp-8]
00402FF4 imul ecx,dword ptr [ebp+8]
00402FF8 mov dword ptr [ebp-8],ecx
115:
116: return Tv_Test2;
00402FFB mov eax,dword ptr [ebp-8]
117:
118: }

<리스트 12> <리스트 10>의 최적화 옵션 사용한 상태
; 107 :
; 108 : int Tv_Test1;
; 109 :
; 110 : Tv_Test1 = A_Test + 2;
; 111 :
; 112 : int Tv_Test2;
; 113 :
; 114 : Tv_Test2 = Tv_Test2 * A_Test;

mov eax, DWORD PTR _Tv_Test2$[esp-4]
imul eax, DWORD PTR _A_Test$[esp-4]

; 115 :
; 116 : return Tv_Test2;
; 117 :
; 118 : }

ret 0

어느 정도 어셈블리를 경험했던 독자는 <리스트 11>과 <리스트 12>의 차이를 보고 놀랄 것이다. 최적화 옵션을 끈 경우 컴파일러는 정석대로 지역변수 할당을 위한 스택 프레임을 만든다.

00402FD0 push ebp
00402FD1 mov ebp,esp
00402FD3 sub esp,48h

스 택 프레임을 만든 후 ebp 레지스터를 스택 프레임의 기준 위치로 잡아 지역변수와 파라미터를 제어할 것이다. 하지만 최적화 옵션을 최대로 했을 경우 VC++는 스택 프레임을 만들지 않는다. 대신 esp 레지스터로 현재 스택 사용량을 내부적으로 계산해 가며 지역변수와 파라미터를 제어한다. 이는 사람이 직접 어셈블리어로 코딩하기에는 힘들고 위험한 방법이다. 그뿐만 아니다. VC++는 해당 루틴의 지역변수 사용량을 검사해 가능한 레지스터만으로 처리가 가능한 연산일 경우 지역변수를 사용하지 않고 레지스터만으로 처리한다. 이처럼 오늘날의 고급 컴파일러의 최적화 성능의 향상으로 인해 어지간한 어셈블리로 만든 루틴보다 고급 컴파일러로 만든 코드의 성능이 더 나은 경우도 많다. 물론 컴파일러도 사람이 프로그래밍해 놓은 상황에 맞춰 최적화시키는 방식이다 보니 복잡하거나 컴파일러가 예상치 못한 상황의 경우 어셈블리어보다 효율이 떨어질 수 있다. 하지만 매우 특별한 상황이 아니라면 고급 컴파일러의 최적화 기능은 프로그래머가 요구하는 성능을 충분히 끌어 낼 수 있다.

최적화하려면 정확한 측정도 중요하지만 어떤 곳에서 데이터의 흐름이 많은 지에 대한 분석 및 통계도 중요하다. 현재 이런 것에 대한 지원 툴로는 인텔의 V-TUNE나 디벨로퍼 스튜디오가 있다. 이런 툴들의 사용법을 정확히 알고 코드를 수정하는 것 역시 좋은 최적화의 길이라 할 수 있다.

성능 측정에서 주의할 점
RDTSC 명령은 CPU 클럭 값을 보여주는 매우 중요한 측정 도구다. 하지만 이 명령으로 측정한 결과가 항상 정확하다고 할 수 없다. 이는 RDTSC가 부정확하게 동작함을 말하는 것이 아니다. 수행시간에 영향을 미치는 다른 요소들이 존재하기 때문이다. 윈도우 애플리케이션의 수행 속도에 영향을 미치는 요소들은 다음과 같다.

컨텍스트 스위칭
윈도우는 선점형 멀티태스크를 지원하는 OS다. 이 말은 애플리케이션의 함수가 수행중일지라도 OS에서 지정한 양의 시간이 지나면 자동으로 다른 프로세스/쓰레드로 전이해 실행한다는 뜻이다. 이를 컨텍스트 스위칭(context swiching)이라 한다. 만일 많은 수행시간을 요하는 연산을 측정할 때에는 다른 프로세스 및 쓰레드의 실행시간까지 같이 합산된 결과가 나온다는 뜻이다. 때문에 매우 긴 연산을 측정할 때에는 가급적 다른 애플리케이션들은 실행하지 않아야 한다.

캐시
CPU보다 상대적으로 느린 메모리 및 주변기기의 속도를 보완하기 위하여 만든 것이 바로 캐시(Cache)이다. 캐시는 매우 빠른 메모리에 사용할 데이터들을 보관하면서 CPU에 빠른 속도로 데이터를 건네주는 역할을 한다. 처음 실행하는 함수의 경우 제어할 데이터가 캐시에 준비가 안 되는 경우가 많기 때문에 함수의 실험은 첫 회의 경우 무시하고 두 번째 클럭 값부터 적용하는 것이 좋다.

가상 메모리의 물리 페이지로 맵핑
윈 도우의 경우 설치된 시스템보다 많은 메모리를 사용할 수 있게 하기 위하여 가상메모리(virtual memory)를 지원한다. 사용 빈도가 낮은 데이터는 하드디스크에 보관했다가 실제로 사용할 경우 데이터를 하드디스크로부터 읽어 실제 메모리에 올려 사용한다는 개념이다. 하드디스크의 속도는 메모리의 속도보다 훨씬 느리다. 때문에 처음 실행하는 함수의 경우 사용할 메모리 영역이 실제 메모리에 올라와 있지 않을 확률이 크다. 이런 경우 함수 실행 시 하드디스크로부터 메모리 영역을 읽어오는 시간으로 인해 매우 많은 클럭 수를 소요하는 것으로 결과가 나올 수 있다. 이는 바로 앞에서 언급한 캐시로 인한 시간손실과는 비교도 안 될 정도로 큰 수치이다. 이런 현상을 방지하는 방법 역시 첫 회의 실험으로 얻은 결과치는 무시하고 여러 번 반복 실험하여 얻은 결과치의 평균을 얻는 방법이다.

그 외의 요소들로는 하드웨어 인터럽트, 버스마스터 기기로 인한 사용 가능한 버스의 대역폭 감소 등이 있다. 이러한 요소로 인한 오차를 줄일 수 있는 방법은 테스트 프로그램만을 실행하고 여러 번 실행하여 평균 값을 내는 것이다.

어셈블리를 안다는 것
연 재를 마치면서 한 가지 말하고 싶은 것은 최적화 실험으로 나온 결과는 자신의 하드웨어 및 소프트웨어적 환경을 기준으로 실험한 결과이기 때문에 자신의 루틴을 더욱 효율적으로 만들기 위한 도구는 되어도 다른 환경의 사람과 루틴의 성능을 비교하기 위한 절대적인 잣대는 아니라는 것이다.

지금까지 3회 연재 동안에 간단한 이론 및 몇 가지 최적화 방법과 실험을 해보았다. 필자는 제품 개발 시 고급 언어를 주로 쓰고 어셈블리어로는 서브루틴 정도밖에 만들지 않는다. 하지만 어셈블리를 앎으로서 고급 언어로 개발할 때보다 빠르고 안정된 프로그램으로 만들 수 있는 도구로서 어셈블리를 사용하기를 바랄 뿐이다. 문의 내용은 http://myhome.hitel.net/~DAMGI의 질문 게시판에 올려주기 바란다.

정리 | 위윤희 | iwish@korea.cnet.com


출처: http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=382