Assembler with LCC-Win32 (2 bugs fixed 9th June 2004)

The routine below does a memory copy using MMX registers. It compares favorably with the run time library's memcpy. Checks are made for alignment and adjusted where necessary.

MemCpy

#include <string.h>
#include <malloc.h>
#include <stdio.h>
#include <intrinsics.h>

void __declspec(naked) mem_cpy(void * dest, void * src, size_t nBytes)
{
    _asm("pushl %edi");
    _asm("pushl %esi");

    _asm("movl 12(%esp),%edi");       // dest
    _asm("movl 16(%esp),%esi");       // src

    // if num bytes is very small go straight to the last section
    // this will also handle zero num bytes
    _asm("cmpl $16, 20(%esp)");
    _asm("jl l8check");

    // align src on 8 boundary. If memory is non-aligned it will seriously
    // affect performance, one cannot align both src & dest
    _asm("movl %esi,%ecx");
    _asm("cld");
    _asm("andl $7, %ecx");
    _asm("jpe l64check");           // jump if even parity
    _asm("movl $8,%eax");
    _asm("subl %ecx,%eax");
    _asm("movl %eax,%ecx");
    _asm("rep");
    _asm("movsb");                  // do the copy
    _asm("subl %eax,20(%esp)        // new value of num bytes [orig - align bytes]


_asm("l64check:");
    _asm("movl 20(%esp),%ecx");       // total number of bytes
    _asm("shrl $6,%ecx");
    _asm("movl %ecx,%eax"); // tmp
    _asm("jecxz l32check");
    _asm("shll $6,%eax");
    _asm("subl %eax,20(%esp)");       // new value of num bytes (orig - 64's)

_asm("l64:");
    _asm("jecxz l32check");           // jump to next section if finished
    _asm("dec %ecx");
    _asm("movq 0(%esi),%mm0");        // load each 64 bytes from source
    _asm("movq 8(%esi),%mm1");
    _asm("movq 16(%esi),%mm2");
    _asm("movq 24(%esi),%mm3");
    _asm("movq 32(%esi),%mm4");
    _asm("movq 40(%esi),%mm5");
    _asm("movq 48(%esi),%mm6");
    _asm("movq 56(%esi),%mm7");
    _asm("movq %mm0,0(%edi)");
    _asm("movq %mm1,8(%edi)");
    _asm("movq %mm2,16(%edi)");
    _asm("movq %mm3,24(%edi)");
    _asm("movq %mm4,32(%edi)");
    _asm("movq %mm5,40(%edi)");
    _asm("movq %mm6,48(%edi)");
    _asm("movq %mm7,56(%edi)");
    _asm("addl $64,%esi");
    _asm("addl $64,%edi");            // add 64 each loop to edi & esi
    _asm("jmp l64");

_asm("l32check:");
    _asm("movl 20(%esp),%ecx");
    _asm("shrl $5,%ecx");
    _asm("movl %ecx,%eax");
    _asm("jecxz l16check");
    _asm("shll $5,%eax");
    _asm("subl %eax,20(%esp)");
    
_asm("l32:");                         // can only be one 32's to copy, no loop
    _asm("dec %ecx");
    _asm("movq 0(%esi),%mm0");
    _asm("movq 8(%esi),%mm1");
    _asm("movq 16(%esi),%mm2");
    _asm("movq 24(%esi),%mm3");
    _asm("movq %mm0,0(%edi)");
    _asm("movq %mm1,8(%edi)");
    _asm("movq %mm2,16(%edi)");
    _asm("movq %mm3,24(%edi)");
    _asm("addl $32,%esi");
    _asm("addl $32,%edi");

_asm("l16check:");
    _asm("movl 20(%esp),%ecx");
    _asm("shrl $4,%ecx");
    _asm("movl %ecx,%eax");
    _asm("jecxz l8check");
    _asm("shll $4,%eax");
    _asm("subl %eax,20(%esp)");
    
_asm("l16:"); // can only be one 16's to copy
    _asm("dec %ecx");
    _asm("movq 0(%esi),%mm0");
    _asm("movq 8(%esi),%mm1");
    _asm("movq %mm0,0(%edi)");
    _asm("movq %mm1,8(%edi)");
    _asm("addl $16,%esi");
    _asm("addl $16,%edi");
    
_asm("l8check:");                     // copy the last remaining bytes
    _asm("movl 20(%esp),%ecx");
    _asm("jecxz end");
    _asm("rep");
    _asm("movsb");

_asm("end:");
    _asm("popl %esi");
    _asm("popl %edi");
    _asm("emms");                       // required to reset registers
    _asm("ret");
}

int main(void)
{
    #define NUM 0x8113
    #define OFF 3
    
    long long t2,t1,t0;
    char * src = malloc(NUM+12); // extra for messing offset alignment tests
    char * dest = malloc(NUM+12);

    memset(src, 3, NUM);
    memset(dest, 0, NUM);

    int i;

    t2 = 0;
    for(i = 0; i<10000; i++){
        t0 = _rdtsc();
        memcpy(dest+OFF, src+OFF, NUM);
        t1 = _rdtsc();
        t2 += t1 - t0;
    }
    xprintf("cycles for memcpy %lld\n", t2/i);
    printf("%d\n", dest[NUM-OFF]);

    memset(dest, 0, NUM);

    t2 = 0;
    for(i = 0; i<10000; i++){
        t0 = _rdtsc();
        mem_cpy(dest+OFF, src+OFF, NUM);
        t1 = _rdtsc();
        t2 += t1 - t0;
    }
    xprintf("cycles for mem_cpy %lld\n", t2/i);
    printf("%d\n", dest[NUM-OFF]);
    free(src);
    free(dest);
    return 0;
}
}

 

Back to main page