여치의 프로그래밍 강좌 #6

조회 수 32469 추천 수 296 2005.07.27 23:55:36
여치 *.232.208.169
/*
과거 모 잡지에 연재했던 프로그래밍 강좌입니다.
퍼가실땐 사전에 동의를 얻으시기 바랍니다.
-유영천
*/

드뎌 마지막 회다.
썩 잘된 강좌라고는 절대 생각하지 않는다. 허나 이전에 없던 (나쁜 측면일지 좋은 측면일지는 알 수 없지만) 스타일의 강좌임은 확실하다고 믿어 의심치 않는다. 그래서 시스템 강좌인지 C/C++강좌인지 헷갈리게 만드는 이 강좌의 마지막은 어셈블리로 장식하도록 하려고 한다.

어셈블리는 어디에 쓰는가?
요새 컴파일러의 최적화 능력은 엄청나다. 따라서 시스템 지식에 웬만큼 빠싹하지 않고서는 컴파일러에서 생성해주는 코드보다 최적화된 어셈블리 코드를 짜기는 어렵다. 또한 더 빠르고 작은  코드를 짠다 해도 들인 시간과 정성을 들일만한 가치가 있는지 생각해 볼 일이다.
그럼에도 불구하고 어셈블리는 알아야한다. 디버깅을 위해서라도 어셈블리는 필수적으로 알아야 한다. C코드를 짤때도 어셈블리를 아는 사람과 모르는 사람의 코드는 다르다.
그 외에도 가끔 어셈블리를 쓸 일이 있다. 가변인자 함수를 직접 만들거나, C/C++문법을 할 수 없는 일들을 해야할 때가 대표적이다.
고급 언어로 할 수 없는 일 중에는 MMX나 SSE등 특수한 기능들을 사용하는 것이 포함된다. 필자 같은 다소 매니아틱한 인간이 아닌 정상적인 프로그래머들이 실제 프로젝트에서 어셈블리를 사용하고자 한다면 아마 이런 특수한 기능들을 쓰기 위해서일 것이다.
어셈을 좋아하는 사람은 많지 않으므로 강좌 또한 이러한 실제 사용할만한 주제에 대해서 다루고자 한다.

어셈기본
레지스터
레지스터는 CPU안에 들어있는 고속 메모리이다. 데이터 이동, 산술연산등의 기능을 가지고 있다고 첫 강좌에서 얘기한 바 있다. x86 CPU는 다음과 같은 레지스터들을 가지고 있다.
특수한 몇 개의 레지스터들을 제외하고는 모두 32비트 레지스터이다.
EAX, EBX, ECX, EDX는 8비트씩 쪼개서 사용할 수 있다. 이는 그 자체로서 쓸모가 있기도 하지만 과거 8086과 호환성을 유지하기 위함이 크다.
16비트 시절만 해도 레지스터 이름 앞에 E가 붙지는 않았다. 이는 32비트 386부터 생긴 것 인데 32비트로 확장되었다는 뜻이다.

아래 열거한 레지스터들 외에도 디버그 레지스터, 플래그 래지스터와 같은 특수한 레지스터들이있지만 유져모드 프로그래밍에선 쓸 일이 없으므로 생략하였다.

스택포인터
ESP
현재 스택의 위치를 가리키는 레지스터. 임의로 조작할 수도 있지만 일반적인 프로그래램을 짤때는 건드리지 않는 것이 좋다. push, pop 명령과 연동된다. push하면 4바이트 감소, pop하면 4바이트 증가한다. 이상하다고 생각할지 모르지만 스택은 미리 일정 사이즈를 확보해놓고 그 상태에서 사용할때마다 0을 향해서 감소해가는 형태이기 때문에 push했을 때 스택포인터가 감소하는 것이다. 메모리 공간에서 Push==할당, pop==해제 라고 생각하면 된다.

베이스 포인터
EBP
일반적으로 함수 진입 시에 스텍 프레임과 함수 안에서 자유롭게 사용하게 될(push, pop) 스택공간과의 경계를 지정하기 위해 사용한다.

물론 ebp를 사용하지 않고 바로 esp로 억세스 해도 되지만(실제로 컴파일러가 최적화할땐 그렇게 한다) 사람이 짤 때는 엄청나게 헷갈린다. 가령 함수에 진입했을 때 첫 번째 인자는 ebp+8의 메모리에 존재하지만 esp 기준으로는 어디가 될지 모른다. push, pop할때마다 esp의 값이 바뀌기 때문에 기준점으론 삼을 수가 없는 것이다.

인덱스 레지스터
인덱스 레지스터는 범용 레지스터들처럼 쪼개서 사용할 수 없다. 32비트 그 자체로만 사용할 수 있다. 데이터 이동에 관련된 특수한 명령들을 사용할 때 source와 destination 어드레스를 지정하는 등 주소지정에 관련된 특수 기능을 가지고 있기 때문에 인덱스 레지스터라고 한다.

ESI
source인덱스 레지스터. 왜 source인고 하니 특수한 데이터 이동 명령movs등을 사용할 때 esi에 source 메모리의 어드레스를 넣어야 하기 때문이다.
그 밖에는 일반적으로 메모리의 주소를 지칭할 때 이 레지스터를 사용한다.

EDI
destination 인덱스 레지스터. ESI와 기본적으로 같고 movs등을 사용할 때 destination 어드레스를 넣어야 한다.

범용레지스터
범용 레지스터는 8비트씩 쪼개서 사용할 수 있다. EAX의 경우 AL(LOW바이트),AH(HI바이트)로 8비트씩, 이를 합쳐서 AX로16비트씩, 32비트로 억세스 할 때는 EAX가 된다. 네 개의 레지스터 모두 같은 규칙을 따른다.
기본적으로 산술 연산에 많이 쓰이고 몇 가지 특수한 기능들을 가진다.

EAX, EDX

곱셈 나눗셈을 할 때는 반드시 이 레지스터들을 사용해야 한다. 연산의 결과가 64비트이거나 혹은 소스의 값이 64비트일 때 하위 32비트가 EAX에 , 상위 32비트가 EDX에 저장된다. 인텔계 컴파일러에서는 함수의 리턴값이 eax레지스터를 통해 전달된다.

EBX
기본적인 범용 레지스터의 기능을 가지고 있다.

ECX
카운터 레지스터라고 부른다. loop나 movs등의 명령과 함께 쓰일 경우 카운터로 쓰인다.

MMX레지스터
MMX레지스터는 펜티엄 클래식 MMX버전부터 추가되었다. 정확히는 물리적으로 존재하는 레지스터가 아니고 FPU 스택을 레지스터처럼 사용한다. MM0부터  MM7 까지 8개가 있다.

SSE레지스터
펜티엄3부터 추가되었다. 32비트 float 데이타 4샘플을 동시에 처리할 수 있는 128비트 레지스터이다. XMM0부터  XMM7까지 8개가 있다.

실습해보기
산술연산
-덧셈

asm_add.jpg



뺄셈

asm_sub.jpg



곱셈

asm_mul.jpg



나눗셈

asm_div.jpg



데이터 이동
데이터 이동은 뭐 특별한게 없다. mov명령만 사용하면 된다. 다만 덩어리로 전송할 수 있는 명령들의 용법을 알아두는 것이 도움이 된다.
위에서 설명한 대로 ecx를 카운터로 사용하고 esi,edi를 사용하면 한 개의 명령으로 4바이트를 넘어가는 메모리 블록을 전송할 수 있다.
다음은 간단한 문자열을 카피하는 예제이다.

asm_single_datamove.jpg


뭔가 그냥 봐도 좀 무식해보이는 것이…노가다도 노가다고 명령어 개수가 많으니까 느릴거 같다.

다음은 덩어리의 데이터를 전송하는 방법이다.

asm_bulk_datamove.jpg



rep는 접두어로 몇몇 명령어 앞에 붙어 명령어를 repeat시킨다. movs는 move string으로 스트링(그냥 덩어리 메모리 블록이라고 생각하면 된다)을 카피하는 전송하는 명령이다.
movsb는 바이트 단위로, movsw는 워드 단위로, movsd는 더블워드 단위로 전송한다.
따라서 ecx 레지스터에 8을 넣고 rep movsb명령을 사용하면 esi가 가리키는 어드레스로부터 8바이트를 읽어서 edi가 가리키는 어드레스에 써넣는다는 얘기가 되겠다.

이 기능들을 이용해서 간단한 메모리카피 함수를 만들어보자.

asm_memcpy.jpg



이 함수는 많이 사용하는 memcpy() 대용으로 써도 된다. 하지만 대부분의 경우 memcpy()가 더 빠르다. memcpy()역시 완전한 어셈블리 코드로 작성되어 있을 뿐만 아니라 메모리 4바이트 정렬까지 고려해서 쓰여져 있기 때문이다. 이 메모리 카피 코드를 예제로 넣은 것은 필자가 처음 어셈블리로 짠 코드가 바로 이것이었기 때문이다. 그 땐 순전히 mmx코드를 사용해서 8바이트 전송을 하려고 어셈블리를 익혔다. 그 당시만 해도 mmx레지스터를 이용한 8바이트 카피가 movsd보다 약간 더 빨랐다. 그러나 요새는 mmx를 이용한 데이터 전송은 의미가 없다. movsd가 더 빠르다. memcpy()보다 빠른 메모리 카피를 만들 생각은 안하는게 좋을 것이다. 그보다는 좀더 유용한 것을 짜보는 것이….


함수호출
어셈블리 코드에서의 함수 호출은 C와 어셈블리를 섞어 쓰는데 있어 꼭 필요하다. 위의 메모리 카피 예제에서 봤듯이 어셈블리로 함수를 만들어서 C로 불러 쓰는 것은 무척 쉽다. 전혀 신경쓸 필요가 없다. 하지만 어셈블리 코드에서 C함수를 호출하기 위해서는 것은 몇 가지 알고 넘어가야한다.

calling convention
이는 함수 호출에 관한 규약을 지칭한다. 호출하고자 하는 함수에 인자를 넘기는 방법, 그리고 인자를 넘기느라 사용했던 스택을 어떻게 원상태로 돌리는지에 대한 규약이다. 일단 짚고 넘어가자.

_stdcall
파스칼형이라고도 한다. Win32 API의 기본적인 함수 호출형태이다. 함수 인자는 오른쪽에서 왼쪽으로 역순으로 push한다. 이때 사용한 스택에 대해서는 신경쓰지 않아도 된다. 함수 안에서 스택을 원래대로 되돌려놓고 리턴한다.

_cdecl
표준 C에서 사용하는 호출형태이다. 인자는 오른쪽에서 왼쪽으로 역순으로push한다. 스택에 대해서는 좀 신경써야한다. 함수 리턴 후 푸쉬한 인자 개수*4만큼 add해야한다.이러한 형태를 가지기 때문에 가변인자 함수는 _cdecl로만 만들 수 있다.

_thiscall
C++에서 클래스의 멤버함수의 기본 호출 형태이다. cdecl과 기본적으로 같지만 this포인터를 ecx레지스터를 통해서 전달한다.

다음은 어셈블리 코드에서의 함수 호출 예제이다.

asm_call_direct.jpg



C로 할수 없는 것들
C컴파일러에서까지 굳이 어셈블리를 쓰려는 이유가 바로 이런 것. 고급 언어로는 못하는 것들이다. 자부심을 가지고 익혀보자.

클럭카운트
펜티엄 클래식부터 64비트 카운터란 놈이 내장되어있다.
전원이 인가된 후 CPU에 들어가는 클럭을 카운트 한다. 요새 CPU가 평균 2GHz쯤 된다고치면..1초에 20억번씩 카운트가 올라가는 것이다. 실로 엄청난 숫자지만 64비트라는 숫자는 더더욱 엄청나게 크기 때문에 클럭 카운터가 64비트를 다 채우고 한바퀴 돌아가려면 시간이 꽤 걸린다.
하드웨어적인 카운터이므로 퍼포먼스 체크나 시간을 잴 때 아주 유용하다.
필자의 경우는 작성한 코드의 성능 체크를 위해 주로 사용한다. rdtsc란 명령으로 이 클럭 카운터값을 얻을 수 있다.

asm_rdtsc.jpg



SSE
SSE는 Strreamed SIMD Extension의 약자이다. SIMD는 Single Instruction Multiple Data의 약자로서 단일 명령으로 복수의 데이터를 처리한다는 뜻이다.
MMX가 SIMD의 대표적인 예인데, MMX의 경우 2바이트 정수 4샘플을 동시 처리할 수 있었지만 SSE는 4바이트 float 4샘플을 동시에 처리할 수 있다. 아마도 Streamed라고 명명된 것은 float타입을 지원하기 때문일 것이라 생각한다. MMX처럼 가짜레지스터가 아닌 진짜 레지스터로 작동하기 때문에 확실히 빠르다.
SSE는 요새 3D그래픽스를 비롯한 멀티미디어 분야에서 상당히 많이 쓰인다. 필자 역시 지금도 많이 사용하고 있으며 엔진 코드 안에 SSE로 작성한 코드가 꽤 많이 존재한다.
3차원 벡터나 4차원 벡터를 사용하는 프로그램에서 SSE는 굉장한 위력을 발휘한다.
기본적으로 SSE는 float만 사용할 수 있지만, 펜티엄 4 코어부터 추가된 SSE2는 4바이트 정수 4샘플을 동시에 처리할 수 있다. 또한 64비트 float 2샘플도 동시에 처리할 수 있다.
SSE의 용법은 무진장하게 많고 명령어 개수도 많다. 여기선 소개 차원에서 간단한 벡터 연산들의 샘플을 보이겠다.
참고로 SSE명령을 사용하기 위해선 VC6.0 sp5에 VC CPU팩을 설치해야한다.이것들은 MS다운로드 사이트에서 얻을 수 있다. 아니면 Visual Studio .NET 2003을 사용하면 된다.

다음은 SSE명령을 사용하여 4차원 벡터 두개를 더하는 예제이다.

sse_add.jpg


다시 한번 강조하지만...
필자가 어셈에 처음 손대게 된 것은 99년이었다. 비교적 최근이라 할 수 있다. 그 당시까지만 해도 '컴파일러가 좋은데 뭐하러 어셈블리를 쓰느냐' 라고 주장하던 풋내기 프로그래머였다. 지금 와서 생각해 보건데 어셈에 손을 대지 않았던 이유가 진정 그것만은 아니었다. 어셈블리 언어 라는게 막연히 어렵게 느껴졌고, 새로 학습해야하는 것이 두려웠던 것이다.
코룸 외전을 개발할 당시 게임코드 중 너무나 맘에 안드는 부분이 있었다. 구조적으로 꽤 문제가 있었기 때문에 게임을 느리게 만드는 큰 이유중 하나였다. 그 부분을 고치자고 제의했지만 시간은 촉박했고 당시 갓 입사한 신입 프로그래머의 의견이 먹힐 리 없었다. 결국 로직의 문제는 그대로 두고 코드를 조금이라도 최적화 시키기로 마음 먹었다. 당시 하이텔 게제동의 MMX강좌를 보며, 또 같이 일하던 L주임에게 물어가며 MMX코드를 작성했다. 느린 스피드를 몽땅 만회할 정도는 아니었지만 약 20%정도의 속도 향상이 있었다. 그때 자신감을 얻어 개인적으로 진행하던 3D프로젝트에 SSE를 적용하게 됐고 어셈블리 코드를 보며 디버깅하는 것도 어느 정도 익숙해지게 됐다. 그 때 그렇게 시작하지 않았다면 아마 지금도 '어셈블리 무용론'을 외치고 있을지도 모르겠다.
말하고 싶은 것은 어셈이라는게 하려고만 하면 의외로 쉽다는 것, 그리고 절대  불필요한 것이 아니니 틈날 때 조금씩이라도 익혀두라는 것이다.
늘 강조하지만 시스템 지식은 많이 알면 알수록 좋다. 시스템 지식이 확실하면 프로그래밍 언어로 뭘 선택하든 떡 주무르듯 할 수 있다.


마치면서…
어느덧 6회분의 강좌를 모두 마치게 됐다. 일에 쫓길때는 강좌 쓰는 것이 꽤나 성가시게 느껴지기도 했다. 지금 와서 되돌아보니 더 신경써서 쓰지 못한게 후회스럽다.
필자가 업계에 발을 들이기 전에 모 컴퓨터 잡지에 실린 다이렉트 X강좌를 읽은 적이 있다. 그 강좌의 필자는 당시 코룸(아마도 2아니면 3)를 개발중인 프로그래머 아무개씨. 나중에 게임회사에 처음 입사하고 나서 그 아무개씨의 대각선 맞은편 자리에 앉게 됐다.
물론 그 강좌는 기술적으로 전혀 도움이 안됐다. 하지만 게임업계에 발을 들이기 위한 자신감이랄지, 동기랄지 그런건 조금쯤 더 불어넣어준 것이 확실하다.
이 강좌로 많은 지식을 전달했을지에 대해선 자신이 없다. 다만 예전에 필자가 그랬던 것처럼 독자 여러분께 동기를 부여할 수 있다면 족하다고 생각한다.

본 악덕필자에게 원고 독촉하느라 고생한 이소프넷 홍보팀 직원들과 6회분의 강좌를 모두 읽어주신(설마 한명도 없으려고…) 독자님들께 감사드린다.

댓글 '1'

등급거부

2006.07.04 21:13:56
*.55.55.30

처음부터끝까지 다읽었씁니다...
정말.. 버릴것하나없는 강좌네요..^^

저로써는 따라갈수없는 갭이.. 느껴집니다.
파일 첨부

여기에 파일을 끌어 놓거나 파일 첨부 버튼을 클릭하세요.

파일 크기 제한 : 0MB (허용 확장자 : *.*)

0개 첨부 됨 ( / )
List of Articles
번호 제목 글쓴이 날짜 조회 수
» 여치의 프로그래밍 강좌 #6 file [1] 여치 2005-07-27 32469
82 여치의 프로그래밍 강좌 #5 file 여치 2005-07-27 44366
81 여치의 프로그래밍 강좌 #4 file 여치 2005-07-27 39515
80 여치의 프로그래밍 강좌 #3 file 여치 2005-07-27 35611
79 여치의 프로그래밍 강좌 #2 file 여치 2005-07-27 32206
78 여치의 프로그래밍 강좌 #1 file 여치 2005-07-27 31415
77 지형도 BSP를 사용하시나요? [1] 초보 2005-07-06 26505
76 DirectX 입문용으로 적절한 책이 있을지요? [2] MiR 2005-03-01 41313
75 [re] DirectX 입문용으로 적절한 책이 있을지요? [1] 여치 2005-03-02 30325
74 BSP/Portal/PVS에 대한... guest 2005-02-18 24416
73 [re] BSP/Portal/PVS에 대한... file 여치 2005-02-19 23562
72 3D가속에 관한 질문 [3] 바하무트 2004-12-29 37337
71 [re] 3D가속에 관한 질문 여치 2004-12-29 27630
70 [질문] HeightMap의 퍼포먼스... [1] croove 2004-11-01 45648
69 [상담] 어떻게 해야할까요? . 2004-08-20 26048
68 [re] [상담] 어떻게 해야할까요? [1] 여치 2004-08-21 29025
67 [질문] 힙메모리 라이브러리에대해서. clever98 2004-08-08 32767
66 [re] [질문] 힙메모리 라이브러리에대해서. [1] 여치 2004-08-08 30327
65 [re] [질문] 단편화의 제거는 어떤식으로 하시나요? [1] 웰치스포도주스 2004-11-23 27817
64 개발 인력에 대한 질문 [1] 방문객 108 2004-07-20 23057



XE Login

天安門大屠殺 六四天安門事件 反右派鬥爭 大躍進政策 文化大革命 六四天安門事件 The Tiananmen Square protests of 1989 天安門大屠殺 The Tiananmen Square Massacre 反右派鬥爭 The Anti-Rightist Struggle 大躍進政策 The Great Leap Forward 文化大革命 The Great Proletarian Cultural Revolution 人權 Human Rights 民運 Democratization 自由 Freedom 獨立 Independence 多黨制 Multi-party system 民主 言論 思想 反共 反革命 抗議 運動 騷亂 暴亂 騷擾 擾亂 抗暴 平反 維權 示威游行 法輪功 Falun Dafa 李洪志 法輪大法 大法弟子 強制斷種 強制堕胎 民族淨化 人體實驗 胡耀邦 趙紫陽 魏京生 王丹 還政於民 和平演變 激流中國 北京之春 大紀元時報 九評論共産黨 獨裁 專制 壓制 統一 監視 鎮壓 迫害 侵略 掠奪 破壞 拷問 屠殺 肅清 活摘器官 障テ社會 誘拐 買賣人口 遊進 走私 毒品 賣淫 春畫 賭博 六合彩 台灣 臺灣 Taiwan Formosa 中華民國 Republic of China 西藏 土伯特 唐古特 Tibet 達償ワ喇嘛 Dalai Lama 新疆維吾爾自治區 The Xinjiang Uyghur Autonomous Region free tibet