Optymalizacja 

Strona g│≤wna

 

Optymalizacja programow pod katem wielkosci kodu
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1.Zerowanie rejestrow
2.Przenoszenie do rejestrow 32bitowych wartosci z zakresu 0-255
3.Sprawdzanie wartosci zwracanych przez funkcje API
4.Wymienianie wartosci rejestrow

Programisci piszacy w asemblerze mysla, ze jesli pisze sie w asemblerze ich
kod jest juz max zoptymalizowany(w koncu to asembler! ;), ale jak sie przekonalem
wiele drog wiedzie do tego samego celu.

1.Zerowanie rejestrow
~~~~~~~~~~~~~~~~~~~~~

a) mov eax,0 5 bajtow ; B0,00,00,00,00
b) xor eax,eax 2 bajty ; 33,C0
c) sub eax,eax 2 bajty ; 2B,C0
d) and eax,0 3 bajty ; 83,E0,00

jak sie okazuje nawet najprostsza operacja moze zajac nawet 5 bajtow, ale
jesli zastosujemy np. xor-a ta sama operacja zajmie 2 bajty w wynikowym kodzie
programu.Czesto wykorzystuje sie wartosc 0 jako paremtr dla jakichs tam
funkcji api

a) standardowe rozwiazanie

push offset szSansSerif ; lpFace ; 5bajtow
push 0 ; pitch and family ; 2b
push 0 ; output quality ; 2b
push 0 ; clipping precision ; 2b
push 0 ; output precision ; 2b
push 1 ; char set identifier ; 2b
push 0 ; strikeout attribute flag ; 2b
push 1 ; underline attribute flag ; 2b
push 0 ; italic attribute flag ; 2b
push 400 ; font weight(normal) ; 5b
push 0 ; base-line orientation angle ; 2b
push 0 ; angle of escapement ; 2b
push 0 ; logical average character ; 2b
push 0Dh ; logical height of font ; 2b
call CreateFontA

laczna ilosc bajtow instrukcji potrzebnych do zapamietania paramertow
wywolania procki CreateFontA zajmie w tym przypadku 34 bajty

b) zoptymalizownae rozwiazanie

sub eax,eax ; 2b
push offset szSansSerif ; lpFace ; 5b
push eax ; pitch and family ; 1b
push eax ; output quality ; 1b
push eax ; clipping precision ; 1b
push eax ; output precision ; 1b
push 1 ; char set identifier ; 2b
push eax ; strikeout attribute flag ; 1b
push 1 ; underline attribute flag ; 2b
push eax ; italic attribute flag ; 1b
push 400 ; font weight(normal) ; 5b
push eax ; base-line orientation angle ; 1b
push eax ; angle of escapement ; 1b
push eax ; logical average character ; 1b
push 0Dh ; logical height of font ; 2b
call CreateFontA

tym razem 27bajtow, w sumie maly zysk w porownaniu z poprzednia procedura
ale czasami te pare bajtow moze sie przydac do czegos innego.

c) serie

Gdy w "serii" musimy zapamietac na stosie zera zamiast

push 0 ; 2bajty
push 0 ; 2bajty
push 0 ; 2bajty
push 0 ; 2bajty
push 0 ; 2bajty
push 0 ; 2bajty
push 0 ; 2bajty
================================
+ 14bajtow
oraz

sub eax,eax ; 2bajty
push eax ; 1bajt
push eax ; 1bajt
push eax ; 1bajt
push eax ; 1bajt
push eax ; 1bajt
push eax ; 1bajt
push eax ; 1bajt
===============================
+ 9bajtow

mozna wykonac to tak

sub eax,eax ; 2bajty
push 7 ; 2bajty
pop ecx ; 1bajt
@save_args:
push eax ; 1bajt
loop @save_args ; 2bajty
================================
+ 8bajtow

d) jesli zamierzamy wyzerowac rejestr edx normalnie robimy to przez np xor edx,edx
ale mozna zrobic to jeszcze prosciej korzystajac z instrukcji cdq(ConvertDoubletoQuad)
Instrukcja cdq powoduje,ze rejestr edx jest wypelniany bitem znaku z eax(31bit) wiec jesli
wiemy ze w eax mamy np 1 to wykonanie instrukcji cdq spwoduje wyzerowanie edx,nalezy
tylko uwazac stosujac cdq, czy bit znaku w eax jest ustawiony czy nie,bo jesli w eax
mielibysmy liczbe np

eax=80000001h=10000000000000000000000000000001b
^bit znaku

to wykonanie cdq spowoduje,ze edx zostanie zapelniony bitem znaku eax czyli 1,
wiec w edx znajdzie sie 0FFFFFFFFh.Instrukcja cdq zajmuje tylko jeden bajt.


2.Przenoszenie do rejestrow 32bitowych wartosci z zakresu 0-255
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

a) mov eax,7Fh 5 bajtow ; B0,FF,00,00,00
b) sub eax,eax 4 bajty ; 2B,C0
mov al,7Fh ; B0,FF
c) push 7Fh 3 bajty ; 6A,FF
pop eax ; 58

Czesto potrzebne jest przeniesienie wartosci z zakresu od 0-FF do rejestru 32bitowego
Rejestr 32bitowy przyjmuje dane w porcjach 32bitowych(no chyba stad ta nazwa ;)
a wiec jesli wykorzystamy w kodzie programu cos takiego

mov eax,4

instrukcja zajmie 5 bajtow ; B0,04,00,00,00
4-ka jest traktowana jako 32bitowa wartosc gdy korzystamy z 32bitowych rejestrow.
Najbardziej zoptymalizowanym rozwiazaniem wydaje sie byc wykorzystanie stosu do
prznoszenia wartosci 0-127 do 32bitowych rejestrow

push 4 ; 6A,04
pop eax ; 58

wow 3 bajty, mimo, ze zajmuje to wiecej miejsca w notepadzie po skompilowaniu kodu
zajmuje mniej bajtow na dysku!Nalezy w tym miejscu wspomniec,ze kompilator zapisze
skrocona forme push-a jesli wartosc bedzie miescic sie w granicach 0-127,jesli przekroczymy
ta wartosc i wymusimy na kompilatorze zapisanie skroconej formy push-a dla dowolnej
wartosci bajtu np definiujac makro:

pushb macro byteval
db 06Ah,byteval
endm

pushb 080h ; zapamietaj na stos 128
pop eax

po wykonaniu tych instrukcji w eax znajdzie sie wartosc 0FFFFFF80h(-80h)ale dlaczego nie
00000080h?Heh liczby z zakresu 128-255 traktowane sa jako liczby ujemne ,jesli
uzyjemny skroconej formy push-a dla takiej liczby,na pozostale miejsca dword-a
zostanie skopiowany znak bajtu(1 binarna oznacza minus,0 plus)czyli w wypadku
wartosci 80h(128dec) bit 1 i tak zostanie zapisany dword na stosie jako rozszerzony
o znak

00000000000000000000000010000000 = 00000080h
^bit znaku
11111111111111111111111110000000 = FFFFFF80h
^^^^^^^^^^^^^^^^^^^^^^^^bity oznaczajace znak

heh, zycie jest jak pudelko czekoladek...;).
Ale jest jeszcze inne rozwiazanie,zamiast "marnowac" 1 bajt uzywajac np

mov eax,255 ; 5bajtow

mozna w zastepstwie tego uzyc

xor eax,eax ; 2bajty
mov al,255 ; 2bajty

3.Sprawdzanie wartosci zwracanych przez funkcje API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

W czasach VC++ i delphy chyba nikt juz nie przejmuje sie wartosciami zwracanymi przez
funkcje api, a czesto wlasnie sprawdzanie wartosci moze skrocic czas debugowania
programu.Ok ale to nie na temat, tak wiec funkcje jak to funkcje zwracaja jakies wartosci,
w przypadku funkcji WinAPI zwracana wartosc zawsze znajduje sie w rejestrze eax.W zaleznosci
od funkcji w eax zwracane sa wartosci np 0,-1,uchwyt pliku itp.Np funkcja CreateFileA
zwraca w eax wartosc -1 gdy nie mamy dostepu do pliku, ktory akurat chcielismy otworzyc.
Ale juz np.funkcja CreateIcon zwraca w eax 0 jesli wystapil blad.W kazdym razie bez winapi.hlp
programista piszacy pod win nie ma latwego zycia ;).Jak sprawdzic czy w eax jakas funkcja
nie zwrocila przypadkiem wartosci informujacej o tym ze wystapil jakis blad

push ...
call LoadBitmapA

w helpie czytamy, ze

"If the function succeeds, the return value is the handle of the specified bitmap.
If the function fails, the return value is NULL. "

push ..
call LoadBitmapA
cmp eax,0 ; 83,F0,00
jz @wystapil_blad

ale chwilka instrukcja cmp eax,0 zajmuje 3 bajty.Czy nie da sie tego zrobic troszke
inaczej?Zamiast cmp mozemy zastosowac operacje logiczne takie jak or i test

call LoadBitmapA
or eax,eax ; 0B,C0
jz @wystapil_blad

lub

call LoadBitmapA
test eax,eax ; 85,C0
jz @wystapil_blad

obie instrukcje ustawia nam flage zerowa jesli eax==0 a wiec daja one taki sam
efekt jak cmp eax,0 ale zajmuja o 1 bajt mniej.Mozna jeszcze ten kod zoptymalizowac

call LoadBitmapA
xchg eax,ecx ; 1bajt
jecxz @wystapil_blad ; instrukcja jecxz zajmuje 2 bajty(tyle samo co skoki jxx short)

Nalezy uwazac korzystajac z jecxz poniewaz jest to skok warunkowy o zakresie -127 do 128
i jezeli przekroczymy ten zakres przy wlaczonej dyrektywie jumps tasm przetlumaczy jecxz na

call LoadBitmapA
xchg eax,ecx
jecxz @dummy
jmp @next
@dummy:
jmp @wystapil_blad
@next:

Funkcje api czasami jako kod bledu zwracaja w eax wartosc -1(0FFFFFFFFh), jak zatem
sprawdzic czy taka wlasnie wartosc zwrocila jakas funkcja np.CreateFileA?Najprostszy
rozwiazaniem jest oczywiscie

call CreateFileA
cmp eax,-1 ; 83,F0,00
je @wystapil_blad

a czy nie ten sam efekt da

call CreateFileA
inc eax ; jesli w eax bylaby wartosc -1 po zwiekszeniu o 1 zostalaby ustawiona
je @wystapil_blad ; flaga zerowa
dec ; jesli nie wystapil blad zmniejszamy wartosc w eax o 1 tak aby
; przywrocic oryginalna wartosc zwrocona przez funkcje

w tym przypadku kod wynikowy bedzie o 1bajt mniejszy

4.Wymienianie wartosci rejestrow
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Np mamy w eax 4 w edx 98 i chcemy, zeby w eax znalazla sie liczba 98 a w edx 4 czyli
zeby rejestry zamienily sie wartosciami.Jak to zrobic

push eax
push edx
pop eax
pop edx

rozwiazanie to zajmuje 4 bajty,mozna jeszcze tak

mov ebx,eax
mov eax,edx
mov edx,ebx

"jedynie" 6 bajtow.Ale istnieje instrukcja xchg(eXCHange - wymien,zamien)
ktorej rozmiar wynosi 1 bajt jesli jednym z rejestrow ktorych wartosci wymieniamy
jest rejestr eax np

xchg eax,edx ; 92h
ale juz
xchg edx,esi ; 87h,0D6h

Jak sie okazuje wiele instrukcji jest zoptymalizowane pod wzgledem wielkosci jesli
rejestrem na ktorym operujemy jest eax np.

add edi,400000h ; 81h,0C7h,00h,00h,40h,00h 6bajtow
add eax,400000h ; 05h,00h,00h,40h,00h 5bajtow

jak widac instrukcja korzystajaca z edi ma rozmiar o 1 bajt wiekszy niz ta sama
instrukcja korzystajaca z eax

Ostatnie slowo
~~~~~~~~~~~~~~

Moze zdawac sie wam ze na cholere ta cala optymalizacja, ale jesli przypadkiem zamiast
nopowania jakiegos fragmentu kodu bedziecie potrzebowac jeszcze jakichs dodatkowych instrukcji
a przestrzen do wykorzystania bedzie bardzo skromna, wiedza o optymalizacji moze sie
baaaardzo przydac.

bart^CrackPl
cryogen@free.net.pl



 

Copyright (c) 2000/2001 by Service for programmers