메모리 복사

직접 구현해본 메모리 복사 코드들을 나열하고 비교해본다.

built-in 버전의 memcpy 를 기준으로 상대적인 속도를 측정해보았다.

테스트 머신은 intel i5 CPU 2.67GHz, 4.00 GB 메모리, Windows 7 64bit 이고,

256 MB 의 메모리를 복사하는데 걸리는 시간을 측정하였다.

memcpy 는 97.59 ms 를 기록했다.

단순 복사

한 바이트씩 복사를 하는 가장 단순한 복사코드

void copy_byte( char* dst, char* src, int cnt )
{
    while ( cnt-- > 0 )
        *dst++ = *src++;
}

가장 심플한 코드이지만 성능은 최악. 347.91 ms 를 기록했고 memcpy 대비 3.5 배 느리다.

4 바이트 복사

4바이트 built-in 데이터 타입을 이용하여 복사

void copy_4byte( char* dst, char* src, int cnt )
{
    int* dst_4 = (int*)dst;
    int* src_4 = (int*)src;
    int repeat = cnt >> 2;
 
    while ( repeat-- > 0 )
        *dst_4++ = *src_4++;
 
    dst = (char*)dst_4;
    src = (char*)src_4;
 
    repeat = cnt & 3;
    for ( int i = 0; i < repeat; ++i )
        dst[ i ] = src[ i ];
}

124.91 ms 로 위보단 조금 빠르지만 memcpy 보단 1.27 배 느리다.

Duff's Device 복사

Duff's Device 기법을 이용한 복사

void copy_duff( char* dst, char* src, int cnt )
{
    int repeat = ( cnt + 7 ) / 8;
 
    switch ( cnt % 8 )
    {
    case 0: do { *dst++ = *src++;
    case 7:      *dst++ = *src++;
    case 6:      *dst++ = *src++;
    case 5:      *dst++ = *src++;
    case 4:      *dst++ = *src++;
    case 3:      *dst++ = *src++;
    case 2:      *dst++ = *src++;
    case 1:      *dst++ = *src++;
            } while ( --repeat > 0 );
    }
}

174.31 ms 로 위 4바이트 복사보다도 느리다. Loop Unrolling 했다고는 하나 루프 안에서의 CPU 명령어 개수가 늘어나 오히려 더 느려졌다.

위 4바이트 복사도 사실 Loop Unrolling 이라고 볼 수 있으며, Duff's Device 버전으로 바꾸려면 8바이트 built-in 변수를 사용하면 될 것이다.

SSE 복사

Streaming SIMD Extension 을 이용한 복사

void temporal_copy( char* outbuff, char* inbuff, unsigned int size )
{
    const unsigned int step = 64; // cache line size
 
    while ( size > step )
    {
        _mm_prefetch( inbuff + 640, _MM_HINT_NTA ); // non-temporal prefetch
        __m128i A = _mm_load_si128( (__m128i*)(inbuff +  0) );
        __m128i B = _mm_load_si128( (__m128i*)(inbuff + 16) );
        __m128i C = _mm_load_si128( (__m128i*)(inbuff + 32) );
        __m128i D = _mm_load_si128( (__m128i*)(inbuff + 48) );
 
        // destination address must be 16-byte aligned!
        _mm_store_si128( (__m128i*)(outbuff +  0), A );
        _mm_store_si128( (__m128i*)(outbuff + 16), B );
        _mm_store_si128( (__m128i*)(outbuff + 32), C );
        _mm_store_si128( (__m128i*)(outbuff + 48), D );
 
        inbuff  += step;
        outbuff += step;
        size -= step;
    }
 
    memcpy( inbuff, outbuff, size );  // 나머지부분은 간단하게 memcpy 로 복사
 
    //_mm_mfence(); // 멀티스레드 환경에서 사용
}

95.92 ms 로 memcpy 보다 1.017 배, 아주 근소하게 빠르다.

_mm_prefetch 는 Software Prefetch 참조

마지막 주석친 _mm_mfence 는 멀티스레드 환경에서 instruction reordering 으로 인한 오류를 없애기 위한 코드이다.