Внимательный читатель наверняка заметил, что я еще ни разу не упомянул об ассемблерных вставках. Почему? На мой взгляд, в их применении нет никакой выгоды.
Во-первых, практически все необходимые программисту команды процессора либо доступны на языках высокого уровня, либо могут быть успешно сэмулированы (см., например, Эмуляция операторов SAR, ROR, ROL в Delphi).
Во-вторых, ассемблерные вставки затрудняют сопровождение и анализ кода. Уж лучше изменить алгоритм или, в крайнем случае, полностью переписать нагруженные процедуры на ассемблере.
В-третьих, ассемблерные вставки уменьшают возможности оптимизации кода компилятором. Например, из-за них компилятор может разместить часто используемые переменные в оперативной памяти, а не в регистрах, и вместо прироста производительности будет получен совершенно противоположный результат.
Рассмотрим это подробнее на примере функции MulRol, которая в цикле выполняет умножение (имитация полезной работы) и вращение данных (имитация необходимости ассемблерной вставки). Ниже приведены четыре различных варианта функции: полностью ассемблерный вариант, вариант на Pascal с эмуляцией, вариант на Pascal с вызовом подпрограммы на ассемблере, вариант на Pascal с ассемблерной вставкой.
function MulRolAsm(count, mul, rol: integer): integer; asm test eax, eax jle @done @loop: imul edx, edx, $BCEBCAD rol edx, cl sub eax, 1 jg @loop @done: mov eax, edx end; function MulRolPas(count, mul, rol: integer): integer; var rol2, temp: integer; begin; rol2:=-rol; while count>0 do begin; mul:=mul * $BCEBCAD; temp:=mul shr rol2; mul:=mul shl rol; mul:=mul or temp; //mul:=mul ROL rol; dec(count); end; Result:=mul; end; function MulRolMix(count, mul, rol: integer): integer; begin; while count>0 do begin; mul:=mul * $BCEBCAD; asm mov edx, mul mov ecx, rol rol edx, cl mov mul, edx end; dec(count); end; Result:=mul; end; function rol32(value, shift: longint): longint; asm mov ecx, edx rol eax, cl end; function MulRolCall(count, mul, rol: integer): integer; begin; while count>0 do begin; mul:=mul * $BCEBCAD; mul:=rol32(mul, rol); dec(count); end; Result:=mul; end; procedure TForm1.Button1Click(Sender: TObject); type TMulRol= function(count, mul, rol: integer): integer; const f: array[1..4] of TMulRol= (MulRolAsm, MulRolPas, MulRolCall, MulRolMix); n: array[low(f)..high(f)] of string= ('Asm', 'Pas', 'Call', 'Mix'); var r: array[low(f)..high(f)] of integer; t: array[low(f)-1..high(f)] of cardinal; i: integer; begin; t[low(f)-1]:=GetTickCount; for i:=low(f) to high(f) do begin; r[i]:=f[i](1234567890, 123, 8); t[i]:=GetTickCount; end; for i:=low(f) to high(f) do Memo1.Lines.Add(Format('%d %d %s', [r[i], t[i]-t[i-1], n[i]])); end;
Мне пришлось немного изменить код эмуляции вращения по сравнению с кодом, приведенным в указанной выше статье, для того, чтобы время работы этого варианта заметно отличалось от варианта с вызовом подпрограммы. После этого места распределились следующим образом:
Result Time Function ============================= -277667881 1641 Asm -277667881 2578 Pas -277667881 2875 Call -277667881 5765 Mix =============================
Comments (4)
Некорректно
Если в MulRolMix перенести
mul:=mul * $BCEBCAD;
в ассемблерную вставку:
то MulRolMix будет работать ненамного медленнее, чем MulRolAsm.
Вывод — нужно быть очень осторожным при использовании ассемблерных вставок, т.к. компилятор/оптимизатор могут давать крайне непредсказуемый код...
rxt
Поддерживаю данную корректировку. Извините, но статья не интересна.
1. Если пишите на асме вставки, обязаны знать способ передачи параметров
1-ый EAX, 2-ой EDX, 3-ий ECX, дальше стек, а MulRolMix по кругу гоняет mov edx, edx.
2. Если пишите на асме, обязаны знать, как располагаются переменные в стеке, где база, и по какому смещению (-/+) обращаться к переменным.
Если игнорировать эти обязательства, то блок asm .. end; не более, чем для красоты. В ином случае, совсем не трудно будет решать такие задачи, как обратные вызовы с вложенными процедурами. Которые компилятор не в состоянии решить, а у некоторых гуру вызывает разрыв шаблона.
Автор поставил целью сделать это медленно, задачу выполнил, нареканий нет.
Вот результат из цели "Давайте сделаем это быстро" (кстати, неплохое название для Вашей следующей статьи ;)):
2rxt
По пунктам.
1. Позвольте спросить: вы чувствуете разницу между ассемблерной вставкой и ассемблерной процедурой? А между статьей и банкнотой? )
2. Тут вы, по существу, излагаете позицию автора.
3. В функциях сравнения часто нужно иметь не двоичный, а троичный результат, чтобы можно было их объединять. В простейшем случае, разумеется, достаточно функции IsLess(a,b).
Некорректно
Предполагается, что MulRolMix имитирует некоторую полезную работу на Pascal. Если ее перенести в ассемблерную вставку, то в процедуре фактически не останется никакой полезной работы. И в этом случае целесообразно процедуру переписать полностью на ассемблере, о чем и говорит автор.