Давай сделаем это медленно
Внимательный читатель наверняка заметил, что я еще ни разу не упомянул об ассемблерных вставках. Почему? На мой взгляд, в их применении нет никакой выгоды.
Во-первых, практически все необходимые программисту команды процессора либо доступны на языках высокого уровня, либо могут быть успешно сэмулированы (см., например, Эмуляция операторов 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 =============================