إذا كنت مثلى مهووس بأداء البرنامج و تقوم بعمل benchmarking بالمللى ثانيه ستعرف أن الداله memcpy لا تصلح ابدا مع عمليات نسخ المصفوفات كبيرة الحجم (و التى تقارب 20 ميجا بايت) حيث وقتها سرعة اداء الداله ستكون ضعيفه و ايضا ستقاس بالثانيه.
داخل العدبد من الـ architecture تجد مصنعيها يقوموا بإضافة دعم النسخ بإستخدام الـ SIMD و ذلك لسرعتها فى الأداء (بالإضافة لوجود الكاش) و من هؤلاء المصنعين Intel و AMD و POWER PC و SPARC و MIPS (هؤلاء هم ما اعرف) و غيرهم.
داخل نظام تشغيل ويندوز توجد نسختان للداله memcpy احداهما تستخدم مع نسخ الـ Debug و هى مكتوبة بلغة الـ C++ لتتيح لك امكانية تنقيح البرنامج و اثناء الـ Release يتم ابدالها بنسخه مكتوبة بالأسمبلى من Intel و هذا هو شكل الإثنان:
نسخة الـ Debug
void * memcpy (void * dst, const void * src, size_t count)
{
void * ret = dst;
while (count--) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
return(ret);
}نسخة الـ Release و تستطيع ايجاد الكود الخاص بها داخل مجلد crt\src\intel الموجود بمسار تثبيت فيجوال سى و الملف اسمه memcpy.asm
و صدقونى سواء كود انتل او ميكروسوفت فالإثنان على درجة واحده من السرعه (كود انتل يقوم بعمل align للذاكره قبل النسخ لجعله أسرع و لكن مع ذلك ايضا النسخ يتم بايت بايت).
توجد اساليب افضل للنسخ اكثر من بايت من الذاكره مثل استخدام rep movsw و التى تنسخ 2 بايت مع كل 4 cycle أو rep movsd و التى تنسخ 4 بايت مع كل 4 cycle و كلاهم اسرع كثيرا من كود انتل و ميكروسوفت. (عدد الـ cycles يختلف من جهاز لأخر تبعا لإصدار المعالج)
إذا ذهبنا إلى PowerPC (الإصدار 440 و 464) سنجد ان نسخ memcpy يشبه كثيرا ما وجدناه مع ميكروسوفت و أنتل و لكن IBM قامت بإصدار Application Note عام 2008 تشرح فيها استخدام كود الأسمبلى لنسخ ما يقارب 64 ميجا بأقل وقت ممكن (من 1000 إلى 1200 ميللى ثانيه).
حتى الأن لازلت اقرأ عن SPARC و MIPS و لا اعرف كيف يتم النسخ بهم.
بالنسبه للـ Application Note الخاصه بـ PowerPC يمكنك تحميله من هنــا
بالنسبه لإنتل فقد قمت بكتابة داله تقوم بالتحقق من امكانيات البروسسور فإن كان SSE متاح فهى تقوم بنسخ 16 بايت كدفعه واحده و إذا كان SSE غير متاح و MMX متاح فيتم استخدامها و يتم نسخ 16 بايت دفعه واحده (استخدم مسجلين mm0 و mm1 لإتمام عملية النسخ) و إذا كان كلاهما غير مدعوم فتوجد حالتين إن كان البروسسور خاصتك هو 32 بت فيتم استخدم rep movsd لنسخ 4 بايت كدفعه واحده و إن كان 64 بت فيتم استخدام rep movsq لنسخ 8 بايت دفعه واحده.
سأترك لك عمل الـ benchmarking على جهازك فجهازى قديم بالكد يدعم SSE2.
بالنسبه للكود فهو حتى الأن يدعم Intel 32 و Intel 64 فقط قم بتعريف الماكرو STRLIB_MEM_64 و ذلك حتى تستخدم كود 64 بت إذا كان البروسسور خاصتك داعم له، فى الواقع هذه الداله قمت بتصميمها خصيصا لمكتبتى StrLib حتى يكون نسخ النصوص من الذاكره اسرع ما يمكن.
توجد تحسينات و اضافات سيتم إلحاقها بهذا الكود فيما بعد مثل دعم مترجم GCC و كذلك اضافة دعم PPC 32bit و PPC 64bit، أترككم مع الكود
void strlib_memcpy(void* dst, const void* src, size_t cnt)
{
#ifndef STRLIB_MEM_64
__asm
{
push esi
push edi
; move pointers to registers
mov esi, src ; copy src value to esi
mov edi, dst ; copy dest value to edi
; check for CPUID support
pushfd ; save eflags
pop eax ; restore eflags in eax
btc eax, 21 ; reverse bit 21
push eax ; save eax
popfd ; pop eax to eflags
pushfd ; save eflags (bit 21 will be set to its true value)
pop edx ; restore eflags in edx
xor eax, edx ; check if bit 31 is changed
jnz COPY_WITH_MOVS ; if CPUID not supported (bit 21 is zero) copy using movsd
; check the feature
mov eax, 1 ; set eax to 1
cpuid ; get cpu feature
bt edx, 25 ; check for SSE support
jc COPY_WITH_SSE ; copy using SSE if available
bt edx, 23 ; check for MMX support
jc COPY_WITH_MMX ; copy using MMX if available
jmp COPY_WITH_MOVS ; copy using movsd
COPY_WITH_SSE:
mov ecx, cnt ; copy number of bytes
mov eax, ecx ; copy ecx to eax
shr ecx, 4 ; divide ecx by 16
shl ecx, 4 ; multiply ecx by 16
sub eax, ecx ; get number of bytes will remain after copy
shr ecx, 4 ; divide ecx by 16
emms ; reset the MMX states
LP1:
movdqu xmm0, [esi] ; copy 16 bytes from src
movdqu [edi], xmm0 ; copy 16 bytes back to dest
add esi, 16 ; increase src pointer
add edi, 16 ; increase dest pointer
dec ecx ; decrement ecx
jnz LP1 ; repeat as long as ecx not zero
emms ; reset the MMX states
mov ecx, eax ; copy remain bytes to ecx
jmp REMAINS ; jump to copy remain bytes
COPY_WITH_MMX:
mov ecx, cnt ; copy number of bytes
mov eax, ecx ; copy ecx to eax
shr ecx, 4 ; divide ecx by 16
shl ecx, 4 ; multiply ecx by 16
sub eax, ecx ; get number of bytes will remain after copy
shr ecx, 4 ; divide ecx by 16
emms ; reset the MMX states
LP2:
movq mm0, [esi] ; copy first 8 bytes from src
movq mm1, [esi+8] ; copy next 8 bytes from src
movq [edi] , mm0 ; copy first 8 bytes back to dest
movq [edi+8], mm1 ; copy next 8 bytes back to dest
add esi, 16 ; increase src pointer
add edi, 16 ; increase dest pointer
dec ecx ; decrement ecx
jnz LP2 ; repeat as long as ecx not zero
emms ; reset the MMX states
mov ecx, eax ; copy remain bytes to ecx
jmp remains ; jump to copy remain bytes
COPY_WITH_MOVS:
mov ecx, cnt ; copy number of bytes
mov eax, ecx ; copy ecx to eax
shr ecx, 2 ; divide ecx by 4
shl ecx, 2 ; multiply ecx by 4
sub eax, ecx ; get number of bytes will remain after copy
shr ecx, 2 ; divide ecx by 4
rep movsd ; repeat copy 4 bytes as long as ecx not zero
mov ecx, eax ; copy remain bytes to ecx
REMAINS:
rep movsb ; copy remain bytes
pop edi
pop esi
}
#else
__asm
{
push rsi
push rdi
; move pointers to registers
mov rsi, src ; copy src value to rsi
mov rdi, dst ; copy dest value to rdi
; CPUID is supported by default
; check the feature
mov eax, 1 ; set eax to 1
cpuid ; get cpu feature
bt edx, 25 ; check for SSE support
jc COPY_WITH_SSE ; copy using SSE if available
bt edx, 23 ; check for MMX support
jc COPY_WITH_MMX ; copy using MMX if available
jmp COPY_WITH_MOVS ; copy using movsd
COPY_WITH_SSE:
mov ecx, cnt ; copy number of bytes
mov eax, ecx ; copy ecx to eax
shr ecx, 4 ; divide ecx by 16
shl ecx, 4 ; multiply ecx by 16
sub eax, ecx ; get number of bytes will remain after copy
shr ecx, 4 ; divide ecx by 16
emms ; reset the MMX states
LP1:
movdqu xmm0, [rsi] ; copy 16 bytes from src
movdqu [rdi], xmm0 ; copy 16 bytes back to dest
add rsi, 16 ; increase src pointer
add rdi, 16 ; increase dest pointer
dec ecx ; decrement ecx
jnz LP1 ; repeat as long as ecx not zero
emms ; reset the MMX states
mov ecx, eax ; copy remain bytes to ecx
jmp REMAINS ; jump to copy remain bytes
COPY_WITH_MMX:
mov ecx, cnt ; copy number of bytes
mov eax, ecx ; copy ecx to eax
shr ecx, 4 ; divide ecx by 16
shl ecx, 4 ; multiply ecx by 16
sub eax, ecx ; get number of bytes will remain after copy
shr ecx, 4 ; divide ecx by 16
emms ; reset the MMX states
LP2:
movq mm0, [rsi] ; copy first 8 bytes from src
movq mm1, [rsi+8] ; copy next 8 bytes from src
movq [rdi] , mm0 ; copy first 8 bytes back to dest
movq [rdi+8], mm1 ; copy next 8 bytes back to dest
add rsi, 16 ; increase src pointer
add rdi, 16 ; increase dest pointer
dec ecx ; decrement ecx
jnz LP2 ; repeat as long as ecx not zero
emms ; reset the MMX states
mov ecx, eax ; copy remain bytes to ecx
jmp remains ; jump to copy remain bytes
COPY_WITH_MOVS:
mov ecx, cnt ; copy number of bytes
mov eax, ecx ; copy ecx to eax
shr ecx, 3 ; divide ecx by 8
shl ecx, 3 ; multiply ecx by 8
sub eax, ecx ; get number of bytes will remain after copy
shr ecx, 3 ; divide ecx by 8
rep movsd ; repeat copy 8 bytes as long as ecx not zero
mov ecx, eax ; copy remain bytes to ecx
REMAINS:
rep movsb ; copy remain bytes
pop rdi
pop rsi
}
#endif
}و الله ولى التوفيق

