대용량의 복사에 적당한 MMX명령을 사용한 memcpy의 구현보다도 더 빨리 하는 방법은 없는가라는 질문은 어쩌면 너무나도 당연하다. 이를 위해 펜티엄3에 처음으로 등장한 SSE명령을 이용하면 가능성이 보인다.

한가지 가능성은, 펜티엄3에서 SSE와 함께 등장한 정수명령을 사용하는 것이다. 그중에서 중요한 두가지는 movntq명령과 prefetchnta명령이다. 이들은 캐쉬의 활용도를 최적화함으로써 성능향상에 기여하도록 고안된 명령들이다. 이들을 이용해 지난번의 구현을 변경하면 다음과 같다.

THRESHOLD EQU 96
PREFETCHDIST EQU 128    ; 본문참조
; void *memcpy(void *dst, void *src, size_t n)
memcpy  PROC C
        push    edi
        mov     edi,[esp+8]     ; dst
        mov     eax,edi         ; return value
        push    esi
        mov     esi,[esp+16]    ; src
        mov     edx,[esp+20]    ; n
        cmp     edx,THRESHOLD
        jb      L1
        mov     ecx,edi
        neg     ecx
        and     ecx,7
        sub     edx,ecx
    rep movsb
L0:     prefetchnta [esi+PREFETCHDIST]
        movq    mm0,[esi]
        movq    mm1,[esi+8]
        movq    mm2,[esi+16]
        movq    mm3,[esi+24]
        movq    mm4,[esi+32]
        movq    mm5,[esi+40]
        movq    mm6,[esi+48]
        movq    mm7,[esi+56]
        add     esi,64
        movntq  [edi],mm0
        movntq  [edi+8],mm1
        movntq  [edi+16],mm2
        movntq  [edi+24],mm3
        movntq  [edi+32],mm4
        movntq  [edi+40],mm5
        movntq  [edi+48],mm6
        movntq  [edi+56],mm7
        add     edi,64
        sub     edx,64
        ja      L0
        emms
        ; sfence
        and     edx,63
L1:     mov     ecx,edx
        and     edx,3
        shr     ecx,2
    rep movsd
        mov     ecx,edx
    rep movsb
        pop     esi
        pop     edi
        ret
memcpy  ENDP

변경된 사항은 아주 단순하다. 우선, 주 루프의 선두에 prefetchnta를 사용하여 미리 앞으로 읽을 메모리를 캐쉬로 끌어들인다. 그리고 메모리를 쓸 때에는 캐쉬를 거치지 않고 바로 쓰도록 한다. PREFETCHDIST는 미리 읽어올 메모리의 주소를 결정하는 매크로 상수인데, 이것을 최적의 값으로 결정하는 방법은 상당히 복잡하다. 인텔의 최적화 설명서는 부록을 할애해서 원리를 개략적으로 설명하고, 정확한 값을 구하기 위해서는 자사의 제품을 구매사용할 것을 권하고 있다. 여기서 주어진 128이란 값은 대충 몇가지 값을 써보고 그중 나아보이는 것을 정한 것이며, 최적의 값은 아니다.

현재의 코드에서는 주석처리 되어 있지만, 여러개의 CPU를 장착한 고급시스템에서는 sfence를 사용해야 할 수도 있다. movntq는 WC메모리 타입에 맞추어 작동을 하는데, 각 CPU가 같은 메모리 영역을 다른 타입으로 보고 있다면, sfence없이는 그 내용의 일관성을 보장할 수 없기 때문이다. 경험상으로는 sfence가 없어도 아직까지 문제를 일으킨 적은 없었으나, 인텔이 사용설명서에 sfence를 사용하기를 강하게 권하므로 쉽게 무시하고 지나갈만한 것은 아니다.

펜티엄3의 새로운 명령들을 사용하여 구현한 memcpy는 두가지 커다란 문제점을 안고 있다. 첫째로, 인텔의 권고대로 sfence를 사용하는 경우에, sfence가 엄청나게 느리다는 것이다. 그래서, 상당히 큰 영역을 복사하는 경우가 아니면, sfence로 인한 손실이 커서 SSE를 사용하는 것이 간단한 rep movsd보다 느릴 가능성이 꽤 있다는 것이다. 둘째로, 경험상으로 보면 movntq는 시스템의 특성을 상당히 많이 탄다. 어떤 시스템에서는 이 명령이 매우 빠른 속도로 작동하는데, 다른 시스템에서는 MMX버전보다 형편없이 느리다. 다른 사람들은 movntqmovq에 비해 느린 이유에 대한 설명도 제시하고 있고, 나름대로 설득력도 있어 보인다.

결론적으로, 펜티엄3에 추가된 명령을 이용한 memcpy는 빠르게 작동할 가능성이 있지만, 그에 못지않게 기존의 것보다 느려질 가능성도 크다. 그러므로, 이를 사용하기 전에는 반드시 속도측정을 먼저 해보아야 한다.

memcpy 구현 연재 목록
[1] 가장 단순한 구현
[2] 주소 정렬
[3] MMX 이용
[4] SSE로 확장
[5] 그외의 고려사항들
[6] 초보가 빠지기 쉬운 함정
Posted by movsd
,