kyuseo의 게임 프로그래밍

어셈블리어(Assembler)와 mmx, fpu를 활용한 빠른(fast) memcpy 소스 코드 # 2 본문

C++ 개발

어셈블리어(Assembler)와 mmx, fpu를 활용한 빠른(fast) memcpy 소스 코드 # 2

kyuseo 2008. 3. 11. 19:00

개요..

 

기계어 어셈블리어(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;

}