Delphi Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
델파이 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
FreePascal/Lazarus
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
델마당
볼랜드포럼 광고 모집

델파이 강좌/문서
Delphi Programming Tutorial&Documents
[47] 프로그램 최적화 기법 몇가지 ^^;
깔쌈보이 [handsome] 6593 읽음    2004-11-12 13:09
///////////////////// 개요 /////////////////////

이 기법은 코드 최적화와는 전혀 무관된 기법일지도 모릅니다.
즉, 코드 최적화와는 동떨어질런지는 모르지만, 프로그램의 최적화면에서는 압도적인 차이를 가지게 됩니다.

가령, 단순 반복문을 보겠습니다.

(1)
I := 0
while I < 100 do  ====> for (int i = 0; i < 1000; i++)
begin
    A[I] := 0;
    Inc(I, 1);
end;

(2)
I := 0
while I < 100 do  ====>  for (int i = 0; i < 100; i+=)
begin
    A[I] := 0;
    A[I + 1] := 0;
    Inc(I, 2);
end;

위의 반복문은 A라는 숫자 배열을 0이라는 값으로 초기화 하는 반복구문입니다.
델파이에서는 꽁수(?)가 아니면 for문의 Loop는 1씩 증가하게 되기 때문에, 2씩 중가를 위해 for 구문이 아닌 while 구문을 사용했을 뿐입니다.

코드적 측면에서, 그리고 코드의 판독력/판별력이나 기타 여러가지 면에서도 당연히 (1)번 예제가 낫다고 하실 분들이 대부분일 것입니다.
하지만 프로그램의 실행 최적화를 보게 되자면 의외로 (2)의 경우가 압도적 우위를 점하게 되며, (1)의 경우보다 이론상으론 최고 2배 가량의 우위를 점하게 됩니다.
그리고 조금 더 고치면 최고 4배 가량의 우위를 점하게 되도록 할 수도 있습니다.
(실제 테스트 결과로는 80% 정도의 차이를 보여주더군요...)

왜 이런 결과가 나오는가에 대해서 설명을 하고 이에 대한 판단과 평가, 응용방법은 여러분께 맡기고자 합니다.




///////////////////// 비교 평가 /////////////////////

C++ 계열의 컴파일러들을 가지고 비교 평가한 자료를 가지고 우선 소개하겠습니다.

(1)
for (int n = 0; n < 1000; n++)
        array[n] = 0;

(2)
for (int n = 0; n < 1000; n+=2) {
        array[n] = 0;
        array[n+1] = 0;
}       

위 코드의 실행 최적화 정도를 보자면, VC++ 6.0을 기준으로...
VC++ 6.0이 100%라면, gcc가 70%, 왓콤이 80% 입니다.
그런데, c# 닷넷이 120% 정도의 의외의 결과를 보여줍니다.

어셈레벨의 소요클럭수를 비교해봐도 분명 닷넷은 물론이거니와 VC++ 6.0이 더 느려야 하는데, 이들이 gcc나 홧콤보다 좋은 결과를 낸다는 거죠...
신기하지 않습니까? 아닌가요? 저만 신기해 하고 있는건가요? --;





///////////////////// 원인과 해설 /////////////////////

이 원인을 이제부터 소개할까 합니다.
위의 결과는 모든 CPU가 다 해당되는건 아니구요...

과거 386계열의 CPU부터 초기 펜티엄까지와 현재의 CPU의 차이라고 봐야 합니다.

가령, 11010111 이런 식으로 명령어가 디코딩된다고 한다면...
이중, 1101은 옵코드고, 0111은 오퍼랜드를 의미하다 보니 하나의 명령어마다 크기가 다르게 됩니다.

즉, jmp far address 같은 경우, 주로 5Byte 인데요... jmp가 1byte, 뒤에 주소가 4byte 입니다. 물론 어떤건 3byte도 있고, 1byte도 있습니다.

어쨌든 길이가 모두 틀린 이들 명령어들을 수행하기 위해서는 CPU 내에서 모두 디코딩을 해야 하는데요,
jmp가 16진수로 eah 값이라고 치면, eah를 cpu가 만나게 되면 뒤에서 4바이트를 더 읽어서. 주소로 처리하는 식이 됩니다.
그런 디코더가 cpu내에 내장되어 있는데. 그게 펜티엄 PRO 인가? 부터의 모델에서는 CPU에 여러개가 내장되어 있습니다.
펜티엄 3의 경우에는 4개가 내장되어 있다고 하네요...

이제부터 아하~ 하시는 분들이 계시겠네요? (읔... 누구나 다 아는걸 가지고 제가 횡설수설 하는건 아닐런지...)

결론은... 이 여러개의 디코더들이 동시에 수행이 가능하다면, 당연히 프로그램의 최적화가 일어나겠죠?
다만 INTEL 계열의 CPU는 각 디코더들이 우선순위에 따라서 처리할 수 있는 최대 옵코드의 길이가 다른 반면, AMD 계열은 다 똑같다는 차이가 있긴 합니다.

자.... 이제 다시 설명을 해드리겠습니다.
어셈블리어 1줄의 명령어들을 가지고 다시 설명을 해 드릴께요...

mov  eax, 1
mov  ebx, 1
mov  ecx, 1
mov  edx, 1

와 같은 방식이고 4개의 디코더가 다 처리가능한 명령 4개라면, 1클럭사이클 동안 위 4줄의 명령이 한꺼번에 처리될 수도 있다는 말이 됩니다.

좀 더 구체화해보겠습니다.

mov  eax, 1
mov  ebx, eax
mov  ecx, 1
mov  edx, ecx

이런 코드가 있다고 가정해보겠습니다.
위 명령어들은 의존적이다 보니 mov  eax, 1을 수행해야만 다음줄 mov ebx, eax를 실행하게 됩니다.

이 경우는 첫번째 디코더만 일하고 나머지 디코더들은 놀게 됩니다.



반면,

mov  eax, 1
mov  ecx, 1
mov  ebx, eax
mov   edx, ecx

이렇게 배치를 한다면, 저 4줄은... 딱 2배가 빨라집니다. 한번에 2개씩의 디코더가 동작을 하게 되기 때문이죠.




///////////////////// 델파이에서의 예제 /////////////////////

위의 원인과 해설을 정리해서 델파이에서의 예제를 들어보겠습니다.

A := 1;
B := A;
C := 1;
D := C;

와 같은 코딩을 하게 되면, CPU내의 디코더들은 하나만 동작하게 됩니다.

하지만,
A := 1;
C := 1;
B := A;
D := C;
와 같은 코딩을 하게 되면 딱 2배의 효과를 발휘하게 됩니다.





///////////////////// 결론 /////////////////////

순차적으로 의존성이 있는 코드를 배치하면 그렇지 않은 경우에 비해서 느려진다는 뜻입니다.
최신 CPU(최소한 펜티엄 3 정도)의 잇점을 고려하지 못한 결과를 낸다는 것이죠.

반대로 말하자면 최신 CPU의 잇점을 최대한 이용할려면 의존적인 코드를 연속적으로 넣지 말라는 뜻이 됩니다.

다시 C++계열의 컴파일러들 만을 가지고 설명을 드리자면...
이러한 문제를 잘 알고 컴파일러 단계에서 이러한 처리를 가장 잘 하는 언어가 VC++ 이라는 것입니다.
그런데, 닷넷이 VC++보다 더 좋은 결과를 낸 것은...
닷넷은 이 문제 말고도 다른 여러가지의 최신 CPU 아키텍쳐를 VC++보다 더 활용을 잘 하기 때문이거든요...

그리고 진짜로 흥미로운 것은, 닷넷 컴파일러입니다. 이 넘은 일부러 컴파일 과정에서 내부적으로 불필요한 코드를 삽입하더군요.
그 불필요한 코드란 바로 이런 프로그램 최적화와 관련된 부분도 있는데요...
가령 A = A; 와 같은 부분 말입니다.
A = A; 와 같은 코드를 강제로 빼서 테스트 하면 2초, 넣으니깐 1초 이런 결과가 나오는걸 확인하실수가 있을겁니다.

가령 반복문이 있다면...
반복문 밖은 전체의 1% 정도 밖에 쓰지 않는 반면 반복문 내부가 99% 가량을 쓴다고 볼 수가 있구요...
실제로 옵티마이징은 가장 cpu타임을 많이 먹는 곳인 반복되는 루프를 대상으로만 해도 충분하다는 결론이 나옵니다.

아...
제가 너무 의존성에만 집중을 했군요...

!!!! 결론을 말씀드리자면... 의존성이 관건이 아니라...
어떻게 하면 CPU내의 디코더들을 동시에 돌게 하느냐라는 것입니다. 즉, 의존성뿐만 아니라 다른 문제로도 단일 디코더만 동작하게 될 수도 있고...
이러한 연유로 프로그램 최적화가 멀어지게 된다는 것입니다.

그리고...
어디까지나 저의 개인적인 생각입니다만... 객체지향이니 패턴이니 XP니 이런것을 익히기 이전에...
우선 이렇게 H/W에 친근하면서도 초보자도 쉽게 터득이 가능한 기초적인 것들이 많이 소개가 되었으면 하는게 제 바램입니다. ^^;





///////////////////// 부록 /////////////////////

(1) 시간 핸들링
많은 개발자분들께서는 시간과 관련되어서는 TTimer 와 GetTickCount()를 적절하게 사용하시리라 생각합니다.
여기에, 저는 한가지를 더 소개하고 싶습니다.
어셈블리어인 rdtsc 를 소개하고 싶습니다.
이 것은 윈도우 OS의 영향을 받지 않는 명령이므로 GetTickCount()보다 더 현실적인 명령어 입니다.

반환값은 64bit 이구요... 그 사용 함수를 소개합니다.

function ReadTSC(): Int64;
begin
     Result := Int64(0);
     asm
        rdtsc
        MOV DWORD PTR Result[4], EDX
        MOV DWORD PTR Result[0], EAX
     end;
end;

var
    A, B, C: INT63;
begin

A := ReadTSC();
Sleep(1000);
B := ReadTSC();
C := B - A;

C의 값은 CPU마다 틀립니다. 대략 1초에 해당하는 CPU의 Hz를 리턴하는데요...
CPU가 P3-550Mhz 라면 550M 과 비슷한 숫자가 반환될거구요... 2.4G 라면 2.4G 과 비슷한 숫자가 반환될거겠죠?
당연히 정확하게 1초에 해당하는 값이 아니란건 아시죠? 그냥 대략값이라고 생각해주세요 ^^;
그리고 이 rdtsc를 이용하는 low level api가 있는데요... 길어서 까먹었는데...
Query라는 단어도 붙고 readtsc란 말도 붙는것 같고.. 으음... 암튼 win32api 도움말 보시면 나옵니다...
즉, 보다 정확한 시간값을 알기 위해서는....
TTimer -> GetTickCount() -> Read어쩌고 하는 API -> rdtsc 라는 것입니다. ^^;




(2) 숫자 연산
a := a * 5;
a의 숫자에 5를 곱한 값을 a에 대입하는 거죠?
이 것은.. a := a + a * 4; 와 같죠? ^^
그리고 이건 또... a := a + a shl 2; 와 같죠? ^^;
그래서 a := a * 5; 보다는 a := a + a shl 2; 가 이론상 몇십배 빠르답니다.
이 것이 제가 위에서 소개한 반복문 안에 들어가게 된다면... 그 결과는 깜짝 놀랄만하죠...

원인을 설명하자면... 곱하기는 그 수에 따라서 소요 클럭수가 틀려지는 놈입니다.
보통 최소 17클럭에서.. 4~60클럭을 넘기도 하죠...
반면 쉬프트와 더하기는 각각 1클럭짜리 명령입니다.
그래서 당연히 수십배까지 빨라질수가 있는거죠...





이 외에도 프로그램 최적화 기법은 많습니다.
아마 이러한 기법에 능숙능란해지면... 이러한 기법이 적용되지 않은 시스템보다 엄청 빨라진 성능땜에 주체를 못할겁니다... ㅋㅋ;


객체지향 프로그래밍, 디자인 패턴, XP 등등... 이러한 것들을 공부하시는 것들도 좋지만...
틈이 나는데로 이러한 최적화 코딩 기법도 많이 연구해주세요... ^^
그래서 저도 모르는 여러가지 기법을 소개받았으면 좋겠습니다. ^^;




-ps-

제가 지금까지 소개한 내용은 INTEL사에서 제공하는 문서에 고스란히 기록되어 있으니 허접설명이 마음에 안 드시는 분은 한번 찾아보시길 바랍니다. ^^;

+ -

관련 글 리스트
47 프로그램 최적화 기법 몇가지 ^^; 깔쌈보이 6593 2004/11/12
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.