kyuseo의 게임 프로그래밍
어셈블리어(Assembler)와 mmx, fpu를 활용한 빠른(fast) memcpy 소스 코드 # 2 본문
개요.. |
기계어 어셈블리어(Assembler)와 mmx, fpu 활용한 빠른(fast) memcpy 소스코드를 작성해봅니다.
개발일지 |
2002/12/30
어셈블리를 이용하여 기본 제어문 및 루프 테스트함 ( if_test, for_test ) 거꾸로 메모리 복사하는 reverse_memcpy 버전 개발. "1234567890" -> "0987654321"
if_test, for_test
기본적인 제어문과 루프의 사용법 습득됨
reverse_memcpy
1 바이트 별로 거꾸로 복사하는 reverse_memcpy 버전은 기존의 memcpy2에 비해 무척이나 느린 현상이 보였다. 그도 당연할 것이 기존의 memcpy2 는 4바이트씩 복사작업을 통하여 루프의 최소화를 하였지만 reverse_memcpy 의 버전은 1바이트씩 복사작업을 해야하기 때문에 기존의 memcpy2에 비해서 4배나 루프가 더 돌기 때문이다. C언어로 제작된 코드와 속도 측정을 위하여 memcpy2_c 버전을 제작하여 비교 분석 해보았으니 오히려 Release버전에서 memcpy2_c 가 훨씬 빠른 기 현상을 목격하게 되었다. 아무래도 코드의 반복성향으로 인하여 컴파일러가 자체적으로 최적화하는 듯 보임 따라서 옵션의 Optimizations 을 Default로 변경해본 결과 올바른듯한 결과물을 얻을 수 있었다.
reverse_memcpy2 역시 생각한대로 루프 도는 회수가 reverse_memcpy2 에 비해서 2배 줄어들기 때문에 속도상 엄청난 빠른 면을 보여주었다. 역시 c 버전을 제작하여 속도 비교하였고 Optimizations: Default 에서 60% 가량 어셈블리 버전이 C버전보다 빠름을 보여주었다. 특성상 복사단위는 2바이트 씩 이며 이후 이것을 수정하여 16비트 이미지 출력 루틴을 손볼 예정이다.
argc, argv
파라메타 argc, argv 를 이용하여 테스트가 좀더 용이 하도록 수정함.
좀더 정확한 실행시간 측정 QueryPerformanceCounter 의 사용으로 좀더 정확한 속도 측정을 할 수 있도록 배려하였다. 때문에 매우 큰 수의 테스트 회수(count) 를 줄이더라도 변화된 속도를 측정할 수 있음으로 테스트 시 시간적으로 절약이 되었음.
작업파일 readme.txt 로 작성함
문제점: 아직은 Optimizations 옵션에 따른 컴파일러가 최적화하는 부분에 대해서 정확히 어느 부분이 생략되는지 명백하게 알 수 없음. 때문에 위 부분처럼 반복적으로 테스트할 시 최적화 부분에 대해 아직 의심을 가지고 있으며 이후 실제 상황(게임의......)을 작업하면서 좀더 정확한 그리고 객관적인 테스트 코드를 가지고 다시 한번 테스트 해볼 필요성을 느끼게 되었다.
Optimizations : Default count = 1, length = 1000
memcpy: 11 memcpy2: 5 memcpy2_c: 26 reverse_memcpy: 19 reverse_memcpy_c: 25 reverse_memcpy2: 11 reverse_memcpy2_c: 16 |
소스코드 |
//============================================================================= // // 제 목: Kyuseo 의어셈블리배우기 // // 제작자: 채경석(kyuseo99@chollian.net) // 시작일: 02/12/30 // // 작업일지: readme.txt // //=============================================================================
#include "stdafx.h" #include "aaa.h" #include "mmsystem.h"
#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif
///////////////////////////////////////////////////////////////////////////// // The one and only application object
CWinApp theApp;
using namespace std;
// 분기조건테스트 int if_test_c( int n ) { int rtn;
if( n == 0 ) rtn = 10; else if( n == 1 ) rtn = 11; else if( n == 2 ) rtn = 12; else rtn = 13;
return rtn; }
int if_test( int n ) { int rtn; __asm { mov eax, n;
cmp eax, 0; jne _j1; mov rtn, 10; jmp _end; _j1: cmp eax, 1; jne _j2; mov rtn, 11; jmp _end; _j2: cmp eax, 2; jne _j3; mov rtn, 12; jmp _end; _j3: mov rtn, 13; _end: }
return rtn; }
// for문테스트(주어진숫자만큼더하기) int for_test_c( int n ) { int rtn = 0; for( int i=0; i<n; i++ ) { rtn += n; } return rtn; }
int for_test( int n ) { int rtn = 0; if( n <= 0 ) return rtn;
__asm { mov ecx, n; mov eax, 0;
_j1: add eax, ecx; loop _j1;
mov rtn, eax; }
return rtn; }
void memcpy2_c( void* dest, void* src, int count ) { if( count <= 0 ) return;
BYTE* psrc = (BYTE*) src; BYTE* pdest = (BYTE*) dest; for( int i=0; i<count; i++ ) { *pdest = *psrc; *pdest++; *psrc++; } }
void memcpy2( void* dest, void* src, int count ) { if( count <= 0 ) return;
__asm { mov edi, dest; // 목적지저장 mov esi, src; // 소스저장 mov ecx, count; // 반복회수 mov edx, ecx; // 반복횟수복사(스텍->스텍이빠르다.) shr ecx, 2; // dword 복사하므로/4 해야하므로쉬프트한다. cld; // 플레그재설정 rep movsd; // DWORD 단위복사
mov ecx, edx; // 저장해둔크기위치복구 and ecx, 0x03; // 크기% 4 한값얻기(하위비트) rep movsb; // 바이트단위복사 } }
// 1바이트씩역순으로복사 void reverse_memcpy_c( void* dest, void* src, int count ) { if( count <= 0 ) return;
BYTE* psrc = (BYTE*) src; BYTE* pdest = (BYTE*) dest + count - 1;
for( int i=0; i<count; i++ ) { *pdest = *psrc; psrc++; pdest--; } }
// 1바이트씩역순으로복사 void reverse_memcpy( void* dest, void* src, int count ) { if( count <= 0 ) return;
__asm { mov ecx, count; // 반복회수 mov esi, src; // 소스저장 mov edi, dest; // 목적지저장 add edi, ecx; // 소스인덱스역순으로설정 dec edi;
cld; _j1: movsb; sub edi,2; loop _j1; } }
// 2바이트씩역순으로복사 void reverse_memcpy2_c( void* dest, void* src, int count ) { if( count <= 0 ) return;
count /= 2;
WORD* psrc = (WORD*) src; WORD* pdest = (WORD*) dest + count - 1; for( int i=0; i<count; i++ ) { *pdest = *psrc; psrc++; pdest--; } }
// 2바이트씩역순으로복사 void reverse_memcpy2( void* dest, void* src, int count ) { if( count <= 0 ) return;
__asm { mov ecx, count; // 반복회수 mov esi, src; // 소스저장 mov edi, dest; // 목적지저장 add edi, ecx; // 소스인덱스역순으로설정 sub edi, 2; shr ecx, 1;
cld; _j1: movsw; sub edi,4; loop _j1; } }
void test() { int i; i = if_test( 2 ); i = for_test( 10 );
char s1[20] = "1234567890abcdefg"; char s2[20] = ""; reverse_memcpy2_c( s2, s1, 10 ); }
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { test();
LARGE_INTEGER start, end; int i,j,rtn;
if( argc < 3 ) { printf( "usage : aaa.exe count length (ex:aaa.exe 100 1024)" ); return 1; }
int count = atoi( argv[1] ); int length = atoi( argv[2] );
BYTE* sz1[1000]; BYTE* sz2[1000]; BYTE szr1[1000]; BYTE szr2[1000];
for( i=0; i<1000; i++ ) { sz1[i] = new BYTE[ length ]; sz2[i] = new BYTE[ length ]; szr1[i] = i; for( j=0; j<length; j++ ) { sz1[i][j] = j; } }
printf( "Optimizations : Default\n" ); printf( "count = %d, length = %d\n\n", count, length );
QueryPerformanceCounter(&start); for( i=0; i<count; i++ ) { memcpy( sz2[i%1000], sz1[i%1000], length ); } QueryPerformanceCounter(&end); printf( "memcpy: %d\n", end.LowPart - start.LowPart );
QueryPerformanceCounter(&start); for( i=0; i<count; i++ ) { memcpy2( sz2[i%1000], sz1[i%1000], length ); } QueryPerformanceCounter(&end); printf( "memcpy2: %d\n", end.LowPart - start.LowPart );
QueryPerformanceCounter(&start); for( i=0; i<count; i++ ) { memcpy2_c( sz2[i%1000], sz1[i%1000], length ); } QueryPerformanceCounter(&end); printf( "memcpy2_c: %d\n", end.LowPart - start.LowPart );
QueryPerformanceCounter(&start); for( i=0; i<count; i++ ) { reverse_memcpy( sz2[i%1000], sz1[i%1000], length ); } QueryPerformanceCounter(&end); printf( "reverse_memcpy: %d\n", end.LowPart - start.LowPart );
QueryPerformanceCounter(&start); for( i=0; i<count; i++ ) { reverse_memcpy_c( sz2[i%1000], sz1[i%1000], length ); } QueryPerformanceCounter(&end); printf( "reverse_memcpy_c: %d\n", end.LowPart - start.LowPart );
QueryPerformanceCounter(&start); for( i=0; i<count; i++ ) { reverse_memcpy2( sz2[i%1000], sz1[i%1000], length ); } QueryPerformanceCounter(&end); printf( "reverse_memcpy2: %d\n", end.LowPart - start.LowPart );
QueryPerformanceCounter(&start); for( i=0; i<count; i++ ) { reverse_memcpy2_c( sz2[i%1000], sz1[i%1000], length ); } QueryPerformanceCounter(&end); printf( "reverse_memcpy2_c: %d\n", end.LowPart - start.LowPart );
Sleep(1000);
return rtn; } |