Ассемблерные вставки в С++
Если тело цикла, написанное на C++ и выполняющее арифметические действия, заменить на его дизассемблированный эквивалент, то такой цикл выполняется медленне. Почему?
Может на пальцах покажешь? Пример кода в студию.
Может на пальцах покажешь? Пример кода в студию.
Извиняюсь за громоздкие листинги.
Вариант на С++
long lAddDiff=*piCurLef++ - *piCurRig++;
while(plCurDif<plEndDiL)
{ *plCurDif++=lAddDiff; }
while(plCurDif<plEndDiM)
{ *plCurDif++=*piCurLef++ - *piCurRig++; }
lAddDiff=*piCurLef++ - *piCurRig++;
while(plCurDif<plEndDiR)
{ *plCurDif++=lAddDiff; }
Вариант на ассемблере(мой самый быстрый)
__asm
{ mov eax,piCurLef
mov ebx,piCurRig
mov ecx,plCurDif
mov edx,lCount01
mov edi,[eax]
sub edi,[ebx]
add eax,4
add ebx,4
Lab00:
mov [ecx],edi
add ecx,4
dec edx
jnz Lab00
mov edx,lCount02
Lab02:
mov edi,[eax]
sub edi,[ebx]
add eax,4
add ebx,4
mov [ecx],edi
add ecx,4
dec edx
jnz Lab02
mov edx,lCount01
mov edi,[eax]
sub edi,[ebx]
Lab04:
mov [ecx],edi
add ecx,4
dec edx
jnz Lab04
}
Все переменные определены до приведенного здесь кода на С++ и ассемблере.
Если показанный здесь блок кода на С++ заменить на показанный здесь ассемблерный блок кода, то время счета увеличится на ~10%.
long lAddDiff=*piCurLef++ - *piCurRig++;
while(plCurDif<plEndDiL)
{ *plCurDif++=lAddDiff; }
while(plCurDif<plEndDiM)
{ *plCurDif++=*piCurLef++ - *piCurRig++; }
lAddDiff=*piCurLef++ - *piCurRig++;
while(plCurDif<plEndDiR)
{ *plCurDif++=lAddDiff; }
Возможно компилятор умеет раскрывать циклы.
Попробуй написать код на асемблере равносильный этому, сделов в каждом цикле 2:
while(plCurDif<plEndDiL-1)
{ *plCurDif=lAddDiff;
plCurDif[1]=lAddDiff;
plCurDif+=2;}
if (plCurDif<plEndDiL-1)
{ *plCurDif++=lAddDiff; }
В нутри первого цикла типа так:
Lab00:
mov [ecx],edi
mov [ecx+4],edi
add ecx,8
dec edx
jnz Lab00
А во втором типа так:
Lab02:
mov edi,[eax]
mov edx,[eax+4]
sub edi,[ebx]
sub edx,[ebx+4]
mov [ecx],edi
mov [ecx+4],edx
add ecx,4
add eax,4
add ebx,4
cmp ecx,[plEndDiMm1] plEndDiMm1=plEndDiM-1
jxx Lab02
Так оно должно испольнятся быстрей
Скомпилируй сишный код с выдачей ассемблерного листинга (для MSVC ключ /Fa) и сравни со своим вариантом. Хороший оптимизирующий компилятор знает про такие инструкции, о которых ты и не вспомнил (stos*, cmov*, j*...). Кроме того, и это очень важно, С-компилятор может выравнивать код и данные в памяти по "удачным" местам, отчего скорость может сильно увеличиться.
На ассемблере имеет смысл писать только часто вызываемые, жутко "замороченные" функции, которые не под силу хорошо оптимизировать С-компилятору.
Кроме того, далеко не факт что в итоге в твоей программе цикл получается. Компиляторы ой как любят их разварачивать :)
Некоторые компиляторы умеют это делать. И уж точно не станут разорачивать цикл с неизвестным заранее числом итераций. Возможно, какие-то компиляторы (если "уговорить" ключами) умеют делать что-то подобное показанному выше, но я не слышал об этом.
Извиняюсь за громоздкие листинги.
Все переменные определены до приведенного здесь кода на С++ и ассемблере.
Если показанный здесь блок кода на С++ заменить на показанный здесь ассемблерный блок кода, то время счета увеличится на ~10%.
Покажи как расчитываются следющие переменные:
plEndDiL, plEndDiM, plEndDiR. Думаю это даст ответ.
Извиняюсь за громоздкие листинги.
Все переменные определены до приведенного здесь кода на С++ и ассемблере.
Если показанный здесь блок кода на С++ заменить на показанный здесь ассемблерный блок кода, то время счета увеличится на ~10%.
Я тут чуток поэксперементировал :). Смотри в присоединенном файле. Там три функции:
fc - "оригинальная" на С
fc1 - первая основная наметка, быстрее чем fc процентов на 5-6 (*
fc2 - добавил то что сумел вспомнить, быстрее оригинальной процентов на 20 (*
(* - я не совсем корректно измерял время выполнения функций, но под рукой нет VTune, поэтому проценты имеют некоторую погрешность.
Опять же результат может быть другой, при другом размере данных. У меня испрользуется достаточно маленький размер.
Некоторые компиляторы умеют это делать. И уж точно не станут разорачивать цикл с неизвестным заранее числом итераций. Возможно, какие-то компиляторы (если "уговорить" ключами) умеют делать что-то подобное показанному выше, но я не слышал об этом.
Ну я видел компилятор под ARM который "сворачивал" два вложенных цикла в пару инструкций :)))
Правда это был баг оптимизатора, но... :)
Что касается компиляторов от МС и Интела, то они достаточно хорошо прощитывают циклы и если надо разворачивают их. Но иногода, бывают и у них косяки, если есть сомнения, надо посмотреть ассемблерный листинг кода вызывающщий сомнения, и станет ясно надо трогать или нет. В 99% случаев, трогать не стоит.