Тема: AHK: Ускорение скриптов машинным кодом
Идея взята из темы Machine code functions: Bit Wizardry. Суть в том, что начиная с версии 1.0.46.08 в DllCall можно вместо имени функции указывать её адрес. Таким образом, если вырезать из какого-либо EXE- или DLL-файла код какой-либо функции и поместить в переменную в скрипте, то эту функцию можно потом вызывать через DllCall и она будет прекрасно работать — при условии, что в ней отсутствуют внешние ссылки, т.е. вызовы каких-то других функций или обращения к внешним данным. Функция должна взаимодействовать с внешним миром только через свои аргументы и возвращаемое значение.
Эффект от применения машинного кода может быть очень велик в случаях, когда нужно обрабатывать большой объём данных. Для примера можно взять задачу, обсуждавшуюся на форуме: AHK: вычисление простых чисел. При поиске в диапазоне до миллиона скрипто-машинный код, приведённый ниже, быстрее чисто скриптового в 160 раз, дальше разница ещё больше. Машинная функция написана мной на ассемблере и скомпилирована при помощи The Go tools for Windows + Assembler.
#MaxMem, 256 ; Нужно, например, если Limit = 100.000.000
Limit = 1000000 ; Проверять от 1 до этого числа включительно.
OutFile = %A_Desktop%\MyPrimes.txt
IfExist, %OutFile%
FileDelete, %OutFile%
time0 := A_TickCount ; Замер времени, в научных целях.
; Вызов скриптовой функции - оболочки машинного кода (см. ниже).
; Возвращает количество найденных простых чисел.
Found := PrimeErat(Buf, Limit, 1) ; 1 - конвертировать в строку, 0 - нет.
time1 := A_TickCount - time0
FileAppend, %Buf%, %OutFile% ; Так сохранится, только если в виде строки.
time2 := A_TickCount - time0 - time1
time := A_TickCount - time0
; Точки между тройками цифр в MsgBox ниже — для разборчивости.
strLimit := RegExReplace(Limit, "(?<=\d)(\d\d\d)(?=\d\d\d$|$)", ".$1")
strFound := RegExReplace(Found, "(?<=\d)(\d\d\d)(?=\d\d\d$|$)", ".$1")
MsgBox,
(
Проверено: %strLimit%
Найдено: %strFound%
Поиск и в строку: %time1% мс
В файл: %time2% мс
Общее время: %time% мс
Сохранено:
%OutFile%
)
; ================= Функция поиска ===================
PrimeErat(ByRef Buf, Limit, ToString)
{
Hex = ; Собственно машинный код, в HEX-виде.
(Join LTrim
5553575689E583EC0C8B5D148B4D1831C083F9020F8CCE0000008D344B89D983
7D1C00740E0FBAEE0066C701322083C102EB09C7010200000083C104894DF489
DFC745FC01000000FC83C70439F70F87850000008B0785C078EFFF45FC89F829
D8D1E8400FBAE600732F505357568B7DF4BB0A00000031C989E64EC606204131
D2F7F380C2304E88164185C075F1F3A4897DF45E5F5B58EB0B8B4DF4890183C1
04894DF489C1F7E0729F3B4518779A48D1E001D8BAFFFFFFFF891089C789C8C1
E00201C739F777048917EBF6D1E883E8028D3C03E970FFFFFF8B45FC0FBAE600
73068B55F4C6020089EC5E5F5B5DC20C00
)
VarSetCapacity(Code, Len := (StrLen(Hex) // 2)) ; Переменная под код.
Char := Offset := -1
Loop, % Len ; Запись кода функции в переменную.
NumPut("0x" . SubStr(Hex, Char += 2, 2), Code, ++Offset, "Char")
VarSetCapacity(Buf, (Limit+1) * 2, 0) ; Под найденные простые числа.
; Вызов машинной функции. Вернёт количество найденных простых.
; Первый аргумент DllCall — адрес переменной, куда был записан код.
Found := DllCall(&Code, "uint", &Buf, "uint", Limit, "int", ToString)
; Если была конверсия в строку, нужно обновить переменную, иначе
; она нормально не отобразится и не запишется в файл.
If ToString
VarSetCapacity(Buf, -1)
Return Found
}
Исходник машинного кода:
CODE SECTION
Start FRAME pArray, uLimit, iStr
USES ebx,edi,esi
LOCALS uCount, uNum, pResCur
mov ebx,[pArray]
mov ecx,[uLimit]
xor eax,eax ; Found = 0, если uLimit < 2.
cmp ecx,2
jl >>.exit
lea esi,[ebx+ecx*2]
mov ecx,ebx
cmp D[iStr],0
je >
bts esi,0
mov W[ecx],2032h ; 2 и пробел.
add ecx,2
jmp > 1
:
mov D[ecx],2
add ecx,4
1:
mov [pResCur],ecx
mov edi,ebx
mov D[uCount],1
cld
.find_next
add edi,4
cmp edi,esi
ja >>.done
mov eax,[edi]
test eax,eax
js .find_next
inc D[uCount]
mov eax,edi
sub eax,ebx
shr eax,1
inc eax
bt esi,0
jnc >
push eax,ebx,edi,esi
mov edi,[pResCur]
mov ebx,10
xor ecx,ecx
mov esi,esp
dec esi
mov B[esi],' '
inc ecx
1:
xor edx,edx
div ebx
add dl,30h
dec esi
mov [esi],dl
inc ecx
test eax,eax
jnz < 1
rep movsb
mov [pResCur],edi
pop esi,edi,ebx,eax
jmp > 1
:
mov ecx,[pResCur]
mov [ecx],eax
add ecx,4
mov [pResCur],ecx
1:
mov ecx,eax
mul eax
jc .find_next
cmp eax,[uLimit]
ja .find_next
dec eax
shl eax,1
add eax,ebx
mov edx,-1
mov [eax],edx
mov edi,eax
mov eax,ecx
shl eax,2
.mark_out
add edi,eax
cmp edi,esi
ja >
mov [edi],edx
jmp .mark_out
:
shr eax,1
sub eax,2
lea edi,[ebx+eax]
jmp .find_next
.done
mov eax,[uCount]
bt esi,0
jnc >.exit
mov edx,[pResCur]
mov B[edx],0
.exit
ret
ENDF