int21h

Optimalizace programů v Pascalu

(asi by to chtělo přepsat do trochu úhlednějšího článku)
(nebo to rozdělit na několik článků podle tématu/druhu optimalizace/triku)


- pracujte s daty o velikosti 4 nebo 8 bytů (podle typu Vašeho CPU, 32 bitů nebo 64 bitů), které budou zarovnané na DWORD, resp. QWORD. Práce s nimi se nesrovnatelně zrychlí

- u FP při kompilaci z IDE je lepší vložit na začátek zdrojového textu toto:
{$A+,B-,C-,D-,E-,F-,G+,H-,I-,J+,K-,L-,M-,N+,O-,Q-,R-,S-,T-,V-,W-,X+,Y-}

- u TP7 se používá ideálně toto:
{$A+,B-,D-,E-,F-,G+,I-,K-,L-,N+,O-,P+,Q-,R-,S-,T-,V-,X+,Y-}

- zrušit "load TURBO.TPL", v nem ponechat jen DOS a SYSTEM

- pracovat namísto pod TURBO.EXE pod TPX.EXE, je tam více paměti pro ladění

- pokud je ve FP proměnná WORD, tak se při výpočtech X*2, X*3, X*4, atd. používá buď trik X+X nebo LEA (na CPU vyšších než Pentium se ještě u sudých násobků používá SHL). Pokud je proměnná ale LONGINT, liché násobky se provádějí pomocí IMUL (pro nižší CPU než Pentium2 je zase ale všechno >2 pomocí LEA). Pro DWORD ale platí totéž, co pro LONGINT (tj. pro 2 je to ADD, pro sudé vyšší LEA, pro liché MUL)!!!

- MMX instrukce dávat jen do low-level procedur (resp. takových, které už nevolají žádné další), protože při CALL uschovává FP všechny MMX registry, což zdržuje!

- množina v TP7 je velká 1,2 nebo až 32 bytů, v FP je do verze 2.0.0 velká 4 nebo 32 bytů...

- je dobré použít i FP přepínač "{$IMPLICITEXCEPTIONS ON}", protože to urychlí volání a opouštění procedur s parametry a zásobníkem.

- FP obecně všechny proměnné, které se nevejdou do 32 bitového registru předává do funkce volané odkazem (tj. jako přes VAR) nezávisle na tom, jak jsou skutečně definované (např. packed record se 2 wordy se předá buď VAR nebo hodnotou, record se 2 longinty je vždy VAR).

- na 486 (a možná i na vyšších CPU) se předpokládá, že podmíněné skoky (včetně LOOPů) nebudou skákat. Pak trvají ideálně 1 takt. Pokud ale skočí, trvají pak min. 3 takty (Stall a Flush Pipe). Je proto lepší psát IF THEN tak, aby skákaly co nejméně (viz. vyprodukovaný ASM kód z FP). U ATHelpu jsou tyto dva takty odděleny lomítkem!

- procedury, které se v programu FP používají jen 1x či 2x (případně vícekrát, pokud jsou krátké) udělat jako inline!

- pokud se v ASM END; částech kódu v FP udá na konec seznam použitých registrů, není nutné použité registry PUSH a POPovat (to udělá případně FP sám, pokud to bude potřeba, v opačném případě jen použije jiné registry než ty naše). Snad kromě ES a DS (ty je nutné uschovat, popř. ještě SS).

- u FP jsou ES a DS nastaveny (oba stejně) na selektor (deskriptoru) dat (haldu), která jsou definována přes VAR, ale prakticky i přes GETMEM. Zjednoduší to assembler (není potřeba je plnit, stačí jen ESI a EDI, do kterých se vloží "názvy" proměnných). GS je většinou nula. FS ukazuje na DOSovou konvenční paměť.

- pozor! "if A <> 0 then" není to samé jako "if not A = 0 then". Je to totožné jen s tímto "if not (A = 0) then". Lepší je vždy testovat podmínky postupně (tj. raději v závorkách!)

- u FP je náhrada za makra z FASM. Buď lze dělat jednoduché 1 řádkové náhrady, nebo lze použít tzv. inline procedury. Stačí za proceduru napsat klíčové slovo "inline" (stejně jako external či assembler) a při volání takové procedury překladač nevloží na místo volání instrukci CALL, ale zkopíruje obsah takové procedury (prakticky se jedná o makro). Hodí se např. pro testy bitu, aby se nemusel dělat cykl a nemusel se stále psát znovu, stačí udělat zrychlenou několika řádkovou metodu testu bitů (převod na binární číslo např. pro textové řetězce) do procedury a při jejím volání se její obsah umístí na adresu, kde by normálně bylo CALL. Názvy parametrů se pravděpodobně upraví na volající. Volaná procedura musí být ale ve stejné jednotce, jinak se volá normálně.

- u FP přesunuje funkce MOVE pouze vyšší RAM (tj. nad >640 kB), pro konvenční je potřeba použít DOSMEMxxx, kdy lze použít GET, PUT nebo MOVE, stejně tak u FILLxxx.

- na posledních typech CPU (P4 a ekvivalentní) netrvá EMMS (nepoužívat na procesorech Itanium) skoro nic... na obyčejném Pentiu trvá asi 50 taktů CPU (pokud není SSE/SSE2, raději FPU pro výpočty vůbec nepoužívat - tam, kde je MMX).

- pokud má CPU instrukce SSE2/SSE (pro výpočet Floating point operations), lze míchat instrukce "FPU" (ale jen ty SSE, ne už X87) a MMX bez toho, aby bylo nutné volat EMMS. SSE lze také míchat s obyčejnými FPU instrukcemi. Pouze MMX a FPU nelze míchat. Pro použití REAL proměnných na SSE/SSE2 registrech, je nutné uvést typ koprocesoru namísto X87 typ SSE nebo SSE2, např. {$FPUTYPE SSE}. SSE lze použít od procesorů PentiumIII či AthlonXP, SSE2 až od Pentium4 nebo Athlon64. SSE a SSE2 používá stejné registry XMMx, MMX používá MMXx mapované do FPUx. Instrukce SSE/SS2 pracují i s obyčejnými registry, stejně tak s registry MMX (pravděpodobně instrukce MMX mohou pracovat s registry SSE (omezeně) na CPU, kde je SSE přítomno, ale pomaleji než s MMX registry, raději viz. dokumentace k SSE/MMX - v FP to není skoro vůbec žádná starost).

- MMX2 je jen jiný název pro SSE, resp. se mu také říká KNI. MMX umí pracovat jen s celočíselnými 64 bitovými typy, MMX2/SSE už umí 128 bitové reálné typy. SSE2 pracuje se stejnými registry jako SSE, avšak přidává nové instrukce. SSE2 se také nazývá jako 128 bitové MMX (rozšiřuje jej a prakticky používá někdy i stejné instrukce, tj. může být (je) i celočíselné).


- při kopírování polí nebo recordů je lepší v FP použít příkaz ":=", protože MOVE se nekóduje přímo do instrukcí, ale volá pomocí CALL. Leda že by bylo nutné zkopírovat jen část pole či data z jednoho bufferu pomocí ukazatele do druhého (pak už to ani jinak nejde).

- pokud je nutné (!) obsloužit přerušení RM, je nutné mít rutinu, jejíž adresa je v konvenční RAM, nebo si nainstalovat prostě CallBack na rutinu v PM. Lepší je používat přerušení přes DPMI.

- Pascal (FP?) bere "X : Array[0..Y] of Char" jako řetězec zakončený nulou (tedy např. Writeln bude pracovat až do prvního znaku = 0, nebo asi do posledního znaku, záleží, co přijde dříve). Je nutné to vyzkoušet (může to být chybné). Je možné tento "řetězec" naplnit pomocí X:='něco'+#0.

- u FP vrací Ofs() vždy Longint a ne Word, jako to dělal TP7 (souvisí to s PM) - vrací celou adresu

- v FP vrací PTR také jen 32 bitový offset (v DS deskriptoru/selektoru) a je to zastaralá funkce...

- FP používá vždy zkrácenou formu vyhodnocování IF Boolean. Plná forma měla u TP7 význam jen tehdy, měla-li např. funkce X ve výrazu "IF FALSE AND X THEN" mít nějaký vedlejší efekt (např. nastavení globální proměnné)...

- MOVE se asi nevyplatí na přesun malých dat (<4 byty), pokud se generuje kód pro MOVS instrukce.

- u FP 2.0+ se namísto MemAvail a MaxAvail musí použít GetHeapStatus

- při používání procedurálních typů je NUTNÉ v FP použít před názvem procedury znak @ pro přiřazení!

- vždy používat v FP {$STACKFRAMES OFF}, aby se zbytečně nevytvářel zásobník tam, kde není potřeba...

- u FP používat tzv. SmartLink: ten uloží do EXE jen ty procedury a data, která jsou skutečně použita (je na ně vytvořen odkaz). Ovšem jen pro finální (RELASE) verzi.

- neskákat na začátek souboru jeho znovu otevřením, pokud není zavřený. Ve Windows to může (občas) způsobovat chybu IO přístupu. Lepší je použít Seek. Ještě lepší je načíst vše, co je potřeba (u PAKů/GRP si načíst hlavičku s offsety do bufferu, resp. alespoň část od prvního požadovaného - optimalizace čtení) najednou postupně od začátku do konce.

- kvůli možnostem 32 nebo 64 bitového kódu u FP používat pro definici ukazatele typ "ptrint" (ovšem jen pro uložení do tzv. ordinálního typu).

- Threading je vlastnost programu, kdy lze udělat některé procedury a proměnné, které spolu souvisí, jako threaded a pokud CPU má HyperThreading, je možné spustit tolik kopií těchto funkcí (BeginThread), které budou využívat příslušný počet kopií proměnné/proměnných, kolik má CPU logických jader. Např. funkce, které testují bity u objektu a případě nastavují změněné banky: takto lze obvykle obsloužit 2 současně. Udělat to lze (bez přídavných jednotek) jen (přímo) pro Windows OS. Použít to jen tehdy, pokud bude engine s efekty pomalý i na 1 GHz CPU (udělat novou variantu EXE pro PentiumII a vyšší). Pokud CPU tuto vlastnost nepodporuje, budou tyto funkce spouštěny postupně (jako na normálním CPU, kdyby se nenapsaly jako "vláknovité"). Udělat proto verzi Windows engine, která bude pro některé funkce HT podporovat (jen pro novější CPU, jako PIV).

- pokud se používá HT, není nutné hned deklarovat všechny proměnné jako ThreadVar, a to ani ty, se kterými pracují HT procedury. TVar je nutné jen tehdy, pokud by bylo nežádoucí, aby jednotlivá vlákna tuto proměnnou přepisovala a přitom jiná očekávala určitou hodnotu. Pokud slouží např. jen jako počitadlo proběhnutých threadů nebo se jen čte, může to být jen jako VAR. Lokální proměnné jsou ale vždy ThreadVar.

- proměnná ReturnNilIfGrowHeapFails by měla být nastavena v programu pod FP na True, jinak při nedostatku paměti bude vyvolána RunTime Error 203. Pro TRUE budou funkce GetMem, atd. vracet NIL (což je v pořádku).

- grafiku načítat do paměti po řádcích tak, aby každý řádek začínal na adrese dělitelné 4 (jako BMP), popř. doplnit na konec předchozího dostatečný počet nul (bytů: 4-((X*BPP) and 3)).

- pokud je cykl FOR a proměnná na řádku za FOR se adresuje přes WITH, je nutné zadat WITH až za FOR (jinak by se vztahovalo jen na proměnné těsně za FOR a ne až na dalšími řádku, kde je to potřeba, např. v těle BEGIN...END;)

- u FP pozor na pořadí sčítání řetězců. Lepší je použít přímo k tomu určenou funkci (CONCAT) a nebo spojovat současně jen 2 řetězce (RET := A + B).

- interní assembler musí v TP7 (a na 99% i pod FP) zachovat alespoň registry DS a BP !!!

- FP používá pro celou aplikaci jen 1 selektor (deskriptor), takže DS ukazuje na všechny proměnné v něm - adresují se jen změnou 32 bitového offsetu. Část z haldy může být odkládána na disk a sama halda je zvětšována průběžně, pokud nároky programu vrostou nad mez uvedenou v Options.

- vhodné je za každé END; napsat do komentáře, co zahajuje tento blok (např. {procedure Ahoj} nebo {while not B do}), aby bylo jasné, k čemu toto END patří.

- INT 21h se nesmí volat v jiné než DOSové verzi programu (pod FP)!

- ideálně je vhodné odsazovat BEGIN i od IFů, které k nim patří a to min. o 2 mezery, ideálně o TAB.

- parametry vložené do zásobníku funkce ve FP začínají na offsetu 12 (možná už na 8 ???)

- zapisovat do VRAM lze Mem[$a000:ofs]:=barva; nebo přes "mov FS:[$a000+ofs],barva". Protože ale FP (alespoň v DOSovém programu) má zařízené, že MEM pole ukazuje do DOSové paměti pod 1. MB, lze používat stejné adresování jako v TP7, takže jde i výše uvedené MEM.

- příkaz na kopírování 2 řetězců + ukázka práce pod PM v ASM (FP):
procedure Kopie (Str : string; var Str2 : string); assembler;
 mov esi, Str
 mov edi, Str2
 xor eax, eax
 lodsb stosb
 mov ecx, eax
@L:
 lodsb
 stosb
 dec ecx
 jnz @L  

- namísto MOV ES:[AX] se dá STOSB použít jedině jako REP STOSB, jinak rozhází cache!!

- v FP "CONST NĚCO : BYTE = 100" se inicializují (konstanty) jen 1x a to při startu celého programu (pak už ne) a i když jsou viditelné jen v bloku, kde byly definovány, tak si hodnotu uchovávají i při jeho opuštění (nejsou na zásobníku). U proměnných to ale je jinak: lokální proměnné jsou inicializovány vždy při spuštění procedury, zatímco globální jen 1x (při spuštění programu), tj. VAR NĚCO : BYTE = 100;

- pokud je definováno "VAR A,B : BYTE", tak příkaz "WORD(A):=100" přepíše i proměnnou B!

- Typ BOOLEAN je v FP/TP7 vyjádřen jako FALSE=0,TRUE>0 (většinou Ord(TRUE)=1), tj. vlastně test IF TRUE THEN je vlastně inverzním testem na 0,tj. "OR AX,AX" a "JNZ TRUE".

- sdělovat Free Pascalu, které registry jsou používány (pokud se používá assembler):
asm...end ['EAX', ... ,'ESI'];

- Pascal nedokáže porovnat STRING a ARRAY OF CHAR pomocí znaku "=". Vždy vyjde FALSE!

- typ SET (množina) nelze testovat pomocí IN, pokud je rozsah větší než BYTE (např. v TP nejde WORD, ale přesto to nehlásí chybu, vždy to bude FALSE!). V FP od vyšší verze než 2 by s tímto neměl být problém (tj. i >256).

- odsazovat blok v BEGIN... END, ale ne samotné tyto příkazy, vždy o 1 až 2 znaky

- bloky, které spolu nesouvisí oddělovat (1 i více) prázdným řádkem (procedury, cykly)

- psát hodně komentářů, nepsat řádky delší než 70-80 znaků

- TP7 a FP (pokud je zapnuté chytré linkování) neumisťuje do kódu EXE procedury ani proměnné/konstanty, které nejsou použity. Např. pokud nadefinujete velké množství konstant typu řetězec a s nimi pracuje jistá funkce, pokud danou funkci nikde nezavoláte, nebudou do kódu přilinkovány ani ty konstanty. Jakmile použijete funkci, objeví se v EXE i velké množství dat pro dané konstanty (pokud použijete konstanty v jiné proceduře, přilinkují se pouze ony).

- je-li (zapnuté) MMX, nepoužívat nikdy NPU (ve Free Pascalu používat přepínače, ale také standardní procedury volané přes body, kdy je jedna pro FPU a druhá pro MMX; pokud je málo možností, např. jen 2, použít raději CMP/IF než volání bodu (typ procedure)).

- NEAR a FAR nemá ve FP význam (vše je přes 32 bitový offset)

- FP dává kód do 1 deskriptoru/selektoru, tj. se všechny CALL a JMP adresují přes 1 offset 32 bitů. Totéž platí pro data definovaná pomocí VAR (pravděpodobně i pro GetMem, New, atd.)

- pro lepší přehlednost je možné, aby název proměnné začínal velkým písmenem, ale před ním by malým písmenem byl ještě její typ (tak je kdekoliv v textu možné zjistit, o co jde), např.: b=byte, bl=boolean, w=word, d=dword, l=longint, c=char, s=string, a=array, r=record, t=type, p=pointer, i=integer, si=shortint, q=qword, r=real, m=mmx, ss=sse, atd.

- test na to, zda ukazatel neobsahuje NIL je možno provést přes Assigned(P). FALSE=nil. Stejně tak lze ale ukazatel testovat přímo jako IF POINTER = NIL THEN...

- funkce, které mají za hlavičkou "assembler" a začínají jako "asm..end" namísto "begin..end" berou všechny parametry, které jsou jim předány jako kdyby před nimi bylo VAR, tj. pokud je změní, změní se vždy i v globálních.

- pokud se volá název funkce v podmínce v ní samé, TP7 to bere jako rekurzivní volání, FP to bere jako test jejího dosavadního výsledku - aby to bylo rekurzivní volání, musí se za názvem funkce uvést dvojice závorek () - neplatí pro TP7 Compability mode!

- FP na rozdíl od TP7 umožňuje, aby funkce vracely i složené proměnné, jako Array nebo Record, tj. nejen jednoduché typy, jako String či Longint.

- pokud jsou v FP zapnuté Uncertain Optimizations, tak se většinou ruší význam VAR před parametry funkce

- Mem[] u FP ukazuje do DOSové paměti (konvenční, <640kB), pole MEM funguje jen u GO32V2 verze!

- pozor! V assembleru zápis "MOV AX,BX+4" neznamená totéž co AX := BX+4, ale že do AX se vloží údaj na adrese o 4 vyšší než ukazuje BX (tj. [BX+4]). Pro přičítání je nutné použít INC nebo ADD!

- aby bylo možné detekovat zvukovou kartu, musí to být ISA (nebo mít emulační mód). Detekce adresy $220 až $280 funguje dobře u SBpro, SB16 a Awe, ale nemusí fungovat u Adlib a ESS688. Zvukové karty na PCI sběrnici mohou poskytnout přes rozhraní své VendorID, IRQ, rozsah I/O portů a paměti.

- pod Windows targetem (FP), pokud je to typ GUI, nelze používat Writeln a Readln pracující s textovou obrazovkou! Ne, že by tyto funkce byly nutné i pro DOS verzi...

- nepoužívat globální proměnné v procedurách (max. pro čtení rozlišení, ale nikdy pro zápis, leda by nic jiného nedělala, např. nastavení rozlišení, detekce do proměnné, která je vždy stejná), taktéž nepoužívat v pod-procedurách proměnné nadřazené procedury.

- indexy a pracovní proměnné definovat jako lokální na zásobníku: nepoužívat globální proměnné nebo proměnné z hlavičky funkce (leda by nebyly už v těle dále potřeba)

- deklarovat proměnné až těsně před blokem (funkcí), která je použije, konstanty ale vždy nahoře

- výsledky vracet jako funkce nebo jako VAR, málokdy přes globální proměnné (tomuto se vyhnout)

- při použití MMX je možnost přesunu dat do VRAM pomocí 2x MOVQ v jednom cyklu (na střídačku), tj. 16 bytů.

- v FP zamknout data a kód (nebo jeho části), které se využívají při obsluze přerušení

- pokud možno nepoužívat rekurzi (volání sama sebe)

- všechny konstanty psát na začátek souboru a pojmenovávat je (nikdy nepsat přímo, vyjma INC, atd.)

- pokud možno neobsazovat žádné vektory (časovač $8,$1C), vše číst pomocí portů (časovač InPort)

- přepínat mezi 6 a 8 bitovou paletou lze pomocí VESA funkce 8h (viz. ASM help EXE nebo VBE3 manuál PDF).

- možnost přepínání True Coloru mezi 24/32 bity tam, kde to karta podporuje. Mezi 15 a 16 bity se přepíná přímo pomocí VESA módu, jak je uvedeno v AT Helpu. Pro přepínání mezi 24/32 bity existují také další módy. Standardně se bere, pokud karta umí jen 32 bitů, že tam, kde je TC, tam je 32 bitů. Pokud ale karta umí i 24 bitů, jako např. ATi, tak ta tam má jen 24 bitů. Přímo 32 bitové módy jsou potom na dalších režimech, které v ATHelpu nejsou:
11B	1280  1024	16.8M		24
11C	 640   400	65536		16
11D	 640   480	16.8M		32
11E	 800   600	16.8M		32
11F	1024   768	16.8M		32
120	1600  1200	  256		 8
121	1600  1200	32767		15
122	1600  1200	65536		16  

- 24 bitů si vezme méně paměti RAM, 32 bitů by mělo být rychlejší...

- pod Linuxem budou na 99% zcela jiné kódy pro VESA režimy než pod DOSem / Windows 9X !!!

- snažit se program napsat tak, aby nepotřeboval obsazovat žádné přerušení. Pokud to nutné je, je vhodné obsadit i RM přerušení (to může nastat např. při přístupu na disk, pokud správce DPMI nepoužívá přístup přímo v PM).

- nepoužívat WideStringy, AnsiStringy a obsluhy výjimek (Exceptions), je to pomalé.

- pokud se testuje např. výskyt znaku v určité skupině pomocí množiny (tj. X IN [´C´,´B´]) je nutné mít na paměti, že test probíhá podle pořadí znaků v ASCII tabulce, ne podle pořadí jejich umístění v množině!!!

- v TP7 je volání procedury přes VAR X : PROCEDURE; asi o 19% pomalejší, než když se taková procedura volá normálně a je přeložená v módu FAR. Naproti tomu (opět v TP7), pokud se rozhoduje mezi 2 procedurami pomocí IF THEN, je if jen o 2% pomalejší než přímé volání (opět FAR).

- u FP je rozdíl mezi přímým a procedurálním voláním tento:
	přímé volání funkce		100.0% času
	volání přes IF BYTE THEN	161.3% času
	volání přes IF DWORD THEN	165.8% času
	volání přes VAR PROCEDURE	108.7% času
		(všechny testy provedeny na AMD Athlon XP 1800+ 1.53 GHz 256 MB RAM Windows 98)  

- v FP se tedy používání typu PROCEDURE na rozdíl od TP7 vyplatí (tam je lepší použít IF), těch pár % navíc není vůbec důležitých, pokud se nejedná o kritický kód (tj. např. často opakovanou dlouhou smyčku, např. při zpracování grafiky).

- pokud se ve FP používá "uncertain optimization", musí se všechny globální proměnné adresovat přes ukazatel a nebo všechny jen přímo! Např. VAR v hlavičce funkce je ukazatel, stejně tak pointer. Není možno je kombinovat (např. ve funkci se volání přímo a přes ukazatel bere jako různá proměnná a tedy např. porovnání přes "=" dá vždy <> nezávisle na tom, že se jedná o jednu a tu samou proměnnou). Lepší je tyto optimalizace nepoužívat, nebo adresovat vše pouze přes hlavičky nebo jen přes global (v rámci jedné funkce).

CSE algoritmus bere tyto pravidla:

- Pokud se něco zapsáno do lokální/globální proměnné(registru) nebo do parametru funkce(procedury), tak to nepřepíše hodnotu, na kterou ukazuje ukazatel (pointer), pokud ukazuje na stejné místo v paměti!
- Pokud se něco zapíše do paměti přes ukazatel (pointer variable, parametr funkce s VAR je ukazatel!), tato hodnota nepřepíše hodnotu L/G proměnné nebo parametr FCE/Proc (jedná-li se o stejné místo v RAM). Hlavní důsledek je, že nelze do jedné proměnné zapisovat a druhou číst, nebo je případně porovnávat (nikdy nebudou stejné).

- nepoužívat žádné interní kontroly překladače, které generují Runtime error (vše sám přes IOResult)

- pokud se přesouvají data (MOVE), jejichž velikost je násobek 8, a je zapnutá optimalizace MMX pro tento úsek programu, použije FP instrukci MOVQ

- v případě, že potřebujeme max. rychlost na 32 i 64 bitových CPU, budeme LongWORD pojmenovávat jako WORD a Longint jako Cardinal. FP nahradí tyto operandy správnou velikostí (Word/Dword, atd.)

- FP při práci s proměnnou typu DWORD používá jen sčítání a přesuny, a to i v případě, kdy je nutné proměnnou vynásobit např. číslem 27, popř. udělat A+B+C*8 (stačí 2 instrukce). Zatímco když nahradíme typ DWORD typem WORD nebo LONGINT, už je použita instrukce IMUL, takže to bude až 10x pomalejší!!! Navíc typ LONGINT nemá oproti typu DWORD výhodu v paměťových nárocích!

- Jak nastavit proměnnou bez znaménka na max. možnou mez? X:=0-1; (stejně rychlé jako $fff...)

- kde to jde, nahradit cykl odčítáním (např. test na jednotlivé bity v bytu nedělat přes FOR, ale přes X-128 > 0 then b7=1, X-64, atd.; popř. pomocí AND a testem na 0, je to ještě rychlejší)...

- porovnání rychlostí některých třídících algoritmů (viz. SORTS.PAS):
	1000x tříděno 1000 čísel:
 Quick    3x 1/18.2sec
  Heap    9x 1/18.2sec
  Comb    6x 1/18.2sec
 Shell    3x 1/18.2sec
 Bubbl    1x 1/18.2sec
Insert    1x 1/18.2sec
Select  149x 1/18.2sec	
	100x tříděno 5000 čísel:
 Quick    2x 1/18.2sec
  Heap    7x 1/18.2sec
  Comb    4x 1/18.2sec
 Shell    2x 1/18.2sec
 Bubbl    6x 1/18.2sec
Insert    3x 1/18.2sec
Select  378x 1/18.2sec
	100.000x tříděno 50 čísel:
 Quick   11x 1/18.2sec
  Heap   33x 1/18.2sec
  Comb   19x 1/18.2sec
 Shell    7x 1/18.2sec
 Bubbl    2x 1/18.2sec
Insert    3x 1/18.2sec
Select   40x 1/18.2sec  
- používat pokud možno obecně ten nejrychlejší algoritmus, který se hodí pro daná data.

- rychlý přesun pole po WORDech, které nemá velikost dělitelnou 2:
	shr     cx,33
        rep     movsw
        adc     cx,cx
        rep     movsb  

- rychlý přesun pole po DWORDech, které nemá velikost dělitelnou 4:
	Shr      ecx,1
	Pushf
	Shr      ecx,1
	Rep      Movsd
	Adc      ecx, ecx
	Rep      Movsw
	Popf
	Adc      ecx, ecx
	Rep      Movsb  

- pro kopírování polí a záznamů je lepší použít buď MOVE a nebo přímé přiřazení:

A(record) := B(record)

A(array) := B(array)

(nekopírovat to po jednotlivých položkách!)


- pokud je potřeba provést v 16bitovém TP7/BP7 32bitovou instrukci, stačí před ni dát prefix 66h (pro data), resp. 67h (pro adresu):

REP MOVSW
->
DB 66h;
REP
MOVSW
(REP MOVSD)


- pokud je potřeba ale uložit i 32bitové číslo, stačí znát op kódy dané instrukce a uložit číslo hned za ni (s použitím prefixu):

MOV CX,$b000
->
DB 66;
MOV CX,$b000;
DW 4
(MOV ECX,$4b000)


- Při skocích pomocí IF, GOTO nebo při volání funkcí a procedur dbát na to, aby byla (daná místa) pokud možno co nejblíže sobě (kvůli Cache). Pokud je nutné několikrát po sobě zpracovat např. stejný obrázek, nedělat to po obrázcích, ale po řádcích, tj. nejprve X krát zpracovat první řádek, pak druhý, atd. (datová cache).

- pokud to VGA karta umožňuje a má dostatek paměti, dá se nastavit šířka logického řádku tak, aby segmenty končily přesně s řádky. Např. u 256 barev a nastavení logického řádku na 1024 se při všech rozlišeních vejde do segmentu přesně 64 řádků (pro rozlišení 320 lze nastavit 512, tedy 128 řádků, ale to nemá význam u 256 barev). U HiColoru se tam musí nastavit 2048, u TC 32 bitů 4096. Nároky jsou pak ale na VRAM vždy asi o 1 kB na řádku větší (na VVRAM to nemá vliv). Bude to ale při použití přepínání banků o dost rychlejší (nemusí se počítat, že bank končí uprostřed řádku, tedy vlastně se dá převod na bank urychlit na pouhé SHR) na kartách, které neumožňují LFB (nebo to zakáže operační systém, např. DOSová verze pod Windows XP)... Jen bank se nebude přenášet najednou celý po 64 kB souvisle, ale po každém řádku, kdy se cílový offset zvětšuje o šířku logického řádku, ale posílá se na něj jen skutečná šířka řádku (tedy na velikost paměti počítače to nebude mít vliv).

- při násobení dvěma je lepší namísto X SHL 1 udělat X+X. Hodí se to ještě i pro 3*X.

- Pro zjištění velikosti externí cache CPU stačí provádět např. několikeré zpracování bloků 64, 128, 256 nebo 512 kB, a pokud v daném bloku trvají průchody 2 a 3 několikrát méně oproti průchodu 1, má daný CPU daný počet kB pro cache. Pro interní cache je možné se u Pentia 1 a výše spolehnout alespoň na 8 kB (max. rychlost).

- REP MOVSD trvá "2+7*n" taktů na 386 nebo 486. Pokud se to nahradí touto sekvencí:
	rep_loop:
		mov eax,[esi]
		add esi,4      ; nebo -4 pro opačný směr
		mov [edi],eax
		add edi,4      ; nebo -4
		dec ecx
		jne rep_loop  
trvá to na 386 "21*n-4", na 486 "8*n-4", ale na Pentiu už jen "3*n" taktů, tedy je to rychlejší než MOVSD! Avšak používá to EAX registr a BTB predikci skoků. Navíc AMD K5 si MOVSD rozloží samo do optimalizované sekvence jednoduchých instrukcí, navíc to vše bez likvidace cache! Kromě toho je MOVSD 6x kratší (bytově) než daná sekvence.

- data, která jsou zarovnána na 4 byty (včetně řetězců) urychlí přístup na ně až 8x !!!

- Rychlá změna krátkého bloku dat; namísto:
	riscy_way:
		mov eax,[esi]	; 1
		add esi,4	; 1
		add eax,[edx]	; 2
		add edx,4	; 1
		add eax,[ebx]	; 2
		add ebx,4	; 1
		add eax,[ecx]	; 2
		add ecx,4	; 1
		mov [edi],eax	; 1
		add edi,4	; 1
		dec ebp		; 1
		jne riscy_way	; 3
				; loop cycles = 17
Lze na 486 (u Pentia to běží přibližně stejně rychle) udělat toto:
		; předem si uložíme ukazatele ...
		lea esi,[esi+ebp*4]
		lea edx,[edx+ebp*4]
		lea ebx,[ebx+ebp*4]
		lea ecx,[ecx+ebp*4]
		lea edi,[edi+ebp*4]
		neg ebp
	intelly_way:
		mov eax,[esi+ebp*4]	; 1
		add eax,[edx+ebp*4]	; 2
		add eax,[ebx+ebp*4]	; 2
		add eax,[ecx+ebp*4]	; 2
		mov [edi+ebp*4],eax	; 1
		inc ebp			; 1
		jnz intelly_way		; 3
					; loop cycles = 12
- Máme pole bezznaménkových WORDů a potřebujeme je vynásobit např. číslem 45 (bez MMX):
		mov ecx,count
		mov esi,start_address
	handle_two_words:
		mov eax,[esi]
		; AGI (tomu se nevyvarujeme)
		lea eax,[eax+eax*4]
		add esi,4
		lea eax,[eax+eax*8]
		dec ecx
		mov [esi],eax
		jne handle_two_words  

- Rychlý přesun dat pomocí koprocesoru (tam, kde není MMX; údajně je to rychlejší než MOVSD):
Procedure FPU_Move(zdroj,cil:pointer;bajtu:word);assembler;
asm  
 push ds
 push es
 mov cx,bajtu
 shr cx,4
 les di,cil
 lds si,zdroj
@topofloop:
 fild qword ptr [si]
 fild qword ptr [si+8]
 fistp qword ptr es:[di+8]
 fistp qword ptr es:[di]
 add si,16
 add di,16
 dec cx
 jnz @topofloop
 pop es
 pop ds  
end;  

- pokud je přístupná MMX technologie, kopírovat bloky dat (např. z VVRAM do VRAM) pomocí MMX: nejprve po 64 bytech (naplnit všech 8 registrů a pak je zase uložit do cíle), zbytek po 8 bytech (po 1 registru) - pokud není velikost bloku dělitelná přímo 64 byty. Mělo by to být zarovnáno na 8 bytů. Pokud není, je nutné nejprve přečíst DWORD (pokud je to zarovnáno jen na 4 byty, pokud ani to ne (nesmí), musí se načíst ještě WORD či BYTE), ten uložit a pak teprve začít číst pomocí MMX. Pokud velikost po zarovnání není dělitelná 8, je nutné zbytek opět dočíst (bloky by měly být dělitelné (adresně a velikostně) alespoň 4 když už ne 8 - to nejde vždy zařídit, navíc by to u nonMMX CPU bylo na obtíž; kdyby se pro MMX mělo zarovnávat na 8, zbytečně by to zvyšovalo nároky na RAM).

- 10 pravidel, jak zrychlit vykreslování grafiky:
a) nevolat funkci pouze pro vykreslení 1 pixelu (každé CALL a RET bere celkem 33 taktů)
b) nepoužívat instrukci OUT pro kreslení (může brát až 70 cyklů sběrnice)
c) napsat specifické funkce pro specifické obrázky (obrázek, který není průhledný, by neměla kreslit funkce, která testuje průhlednost. Pokud se má kreslit celý (bez výřezu), nebo bez zrcadla, neměla by to funkce testovat, tedy napsat zvláštní funkci - když bude obrázků hodně, bude těch pár taktů navíc znát).
d) Pokud se používá násobení nebo dělení např. pro výpočet adresy, vypočítat to jen 1x a uložit do proměnné nebo do mini-tabulky. Pak už jen brát vypočtenou hodnotu a např. ji zvětšovat (pro další řádek zase vzít z tabulky, atd.).
e) Pro zápis do VideoRAM nepoužívat 8 bitový zápis. Tam, kde to jde, použít 16 bitový, protože bere stejně času (tj. je 2x rychlejší). 32 bitový zápis používat jen na sběrnici, která ho podporuje (dneska všechny) a kde instrukce REP MOVSD nebude dělat konflikty se zvukovým DMA.
f) Uložte si obrazová data tak, jak budou zobrazena ve video paměti na kartě. Taktéž by měla být zarovnána. Zápis 16 bitového údaje na lichou adresu bere 2x tolik času než na sudou. Zarovnání na 32 bitů bude stejně rychlé, jako přenos pouhých 8 bitů (při nezarovnání může být ale až 4x pomalejší!).
g) Pokuste se zapisovat obrázky na sudé adresy (ideálně dělitelné 4) co nejvíce bude možné. Pokud to někdy nevyjde a dostaneme lichou adresu (zvláště u BPP=3 žádný problém), měla by se napsat zvláštní funkce, která nejprve přenese část dat nutných pro zarovnání a pak zbytek už zarovnaných, popř. ještě zbytek dat menších než jednotka zarovnání na konci). Vždy se zapisuje po řádcích (pokud je obrázek menší než celá plocha na šířku - jinak je dobré ho zapsat sekvenčně celý přes řádky), tedy je rozdíl, jestli se řádek přenáší celý, nebo po 2 až 3 částech.
h) Všechna data (nejen grafická) by měla být pokud možno zarovnána na 32 bitů (na 32 bitových CPU). Pokud mají obrázky liché délky řádků v bytech, měly by se na konec řádků dát zarovnávací byty, aby každý řádek začínal na DWORDové adrese. To také platí pro různé tabulky, atd. Využívat toho, že např. známe některé šířky obrázků, např. napsat zvláštní funkce pro zobrazování obrázků 16x16, atd. tam, kde to ušetří pár instrukcí (pokud bude ovšem úspora menší, než rozhodování před kreslením, kterou z těch všech funkcí použít pro daný obrázek)...
i) Provádět optimalizace i na úrovni assembleru
j) I rychlý kód jde ještě zrychlit :-)

- FP potřebuje pro přeložení konstrukce INC(I,P) jen 1 instrukci (2, pokud nemají shodnou velikost, ale to je vedlejší), avšak pro "I := I+P" potřebuje už 5 instrukcí! Pro INC(I) stačí 1, pro "I:=I+1" jsou potřeba 3 (stejně tak pro "INC(I,5)").

- při zmenšování spritů stačí jen jednoduše přesouvat bloky dat (nikoliv samotné pixely). Pokud se např. zmenšuje z 800x600 (1:1) na 640x480, tak stačí vždy každý 5. řádek vynechat (při něm se nezvýší ukazatel řádku, tj. ten další půjde na "jeho místo"), a na každém řádku z těch 4 stačí vždy načíst 4 pixely (po X bytech BPP) = blok (bloků bude "640 div 4") a přesunout je z pozice BLOK*5 na pozici BLOK*4. Toto je ale metoda, kdy se nepočítá s antialiasingem.

- v Pascalu lze používat závislé cykly:
	FOR A := 0 TO 50 DO
	 FOR B := A+5 TO 70 DO  

- u myši lze kolečko používat také přes INT 33h...

- přetypovat lze proměnnou i na typ záznam, tj. WORD na array[0..1] of BYTE, stejně tak lze z bytu udělat boolean (False je pouze 0, ostatní jsou True), nebo lze přetypovat XMSokno^ na jiný typ (Record), atd. Omezení viz. dokumentace FP.

- Ideální je, když jsou všechny proměnné zarovnané na adresu dělitelnou 4 (bez ohledu na velikost) a ještě lepší (pro práci s nimi) je, když mají velikost rovnou 4 (32 bitový CPU).

- význam direktivy $M pro alokaci paměti v TP7:

stack

= alokuje právě tolik bytů pro zásobník

min

= je-li na heapu méně než MIN bytů, program se ani nespustí a DOS vypíše své hlášení

max

= je-li na heapu méně než nebo rovno MAX, přidělí se programu celý heap

(je-li na heapu více, přidělí se pouze MAX a zbytek je pro programy v EXEC)


- nastavit kvalitu kódu FP na co nejvíce optimalizací, pro rychlejší kód a pokud možno se oprostit jak od Delphi, tak od TP7 (co nejmenší kompatibilita), ale využívat např. C operátory. Žádné debug informace ve finální verzi!

- Pokud se množina pomocí ABSOLUTE umístí na adresu nějaké proměnné, může se použít k nastavení jejích bitů, protože 1 prvek = 1 bit (jen je nutné mít na paměti, že prvky v množině jdou opačně než bity v proměnné).

- pokud karta nepodporuje LFB a je nutné používat BS, tak to nedělat tak, že se posílal celý bank bez ohledu na to, kolik bytů bylo změněno. Nejprve zjistit, které bloky obrazovky byly změněny. Při změně rozlišení se musí vypočítat tabulka, kde bude udáno, na kterém řádku a kterém pixelu (pro aktuální BPP) začíná daný bank a jak jsou všechny velké. Pak při zobrazování se nejprve zjistí, ve kterých bancích leží jednotlivé bloky a postupně se budou zapisovat. Vždy se zjistí začátek bloku, zjistí se jeho bank/banky (např. na hranici), najde se offset v banku (pouhým odečtením VelX*PočY*BPP) a pošlou se data z VVRAM do VRAM (do akt.banku) jen od této adresy a jen tolik bytů, kolik je skutečně změněno (pokud jsou dva a více bloků za sebou změněných, berou se jako jeden), popř. kolik zbývá do konce banku (zbytek se pošle v dalších bancích...).

- lze definovat proměnnou, která udává délku řetězce, aniž by se musela alokovat další paměť nebo používat přetypování:
	var	retezec : string;
		delka : byte absolute rezetec;  

- stejně tak lze adresovat např. textovou video paměť bez nutnosti vytvářet proměnnou na haldě:
	var	vram :  record
			 Znak : char;
			 Atr : byte;
			end absolute $b800:0;
		(toto nemusí fungovat dobře pod Free Pascalem !!!)  

- výmaz celé textové obrazovky pomocí FillChar(Mem[$b800:0],4000,0) má nevýhodu v tom, že všechny znaky, které se pak budou psát jen pomocí Write budou černé na černé. Lepší je to ve FP provést pomocí FillWord(Mem[$b800:0],2000,7) nebo ještě lépe (jistější) dpmi_dosmemfillword($b800,0,2000,7) s jednotkou GO32;

- v FP je pro přiřazení funkce do procedurální proměnné nutné dát před funkci znak @.

- ve FP je možné hodnotu funkce ve funkci i testovat, ale je nutné ji uvést jako JMENO. Pokud se uvede jako JMENO() v případě, že nemá parametry, nebo jako JMENO(params) v případě, že je má, bude se to brát jako rekurzivní volání! Viz. "Porting from TP7" v "More Information" na stránkách www.freepascal.org !
- pro informace o vývoji her v FP, viz.:
- zdrojové příklady je možné najít na .

- pokud je v jednotce definována procedura a v programu, který jednotku používá, je definována proměnná se stejným jménem, je sice možné v jednotce volat proceduru jako proceduru, ale program bude pod tímto názvem vidět jen a pouze svou proměnnou! Lze se tomuto vyhnout tečkovou konvencí, kdy se před název proměnné dá název jednotky následovaný tečkou a hned je z toho procedura. Lze pak použít obojí najednou. Proměnnou z PROGRAM ale nelze nikdy použít v jednotce.

- čtení náhodných čísel z času hodin, resp. z časovačů vytváří náhodná čísla, avšak vytváření čísel pomocí RANDOM přes RANDOMIZE má za následek sice jen pseudonáhodná čísla (budou se po určité době opakovat), avšak každé číslo bude za určitý čas vráceno jen 1x než se vystřídají všechny, tj. jejich výběr bude rovnoměrnější.

- jednotky jsou inicializovány v pořadí, v jakém jsou uvedeny v USES a to zleva doprava.

- typové konstanty (inicializované proměnné) definované v procedurách jsou přístupné pouze v nich, avšak po jejich opuštění neztratí svou hodnotu (uchovávají se jako globální!)

- pomocí absolute lze na stejnou adresu uložit proměnné různého typu, je to obdoba variantního (case) záznamu...

- FP umožňuje použít tzv. přetížené funkce a proměnné, lze tedy definovat např. funkci pro sčítání, která má parametr byte, druhou se stejným jménem pro word a překladač vybere vhodnou proměnnou až při volání tohoto jména podle velikosti proměnné nebo počtu parametrů.

- v TP porovnávání Ptr ukazatelů se porovnává zvlášť SEG i OFS, tj. i stejné lineární adresy mohou způsobit nerovnost! Jedině porovnávání samotných pointerů (normalizovány na offset = 0) dává shodné výsledky (při rovnosti Segmentů a tedy i lin.adres)

- max. velikost přenesených dat najednou (TP7) v BlockRead nesmí překročit 64kB (jinak se přenese méně, tj. MOD 65536. je nutné to rozdělit do více BlockReadů. Pozor na součin VELBLOKU*POCETBLOKU, standardně je VELBLOKU 128 bytů! Pokud se v RESET neuvede jinak). Platí jen pro TP7!!!

- WORD lze také zapsat jako ARRAY[0..1] OF BYTE, kdy [1] je HI a [0] je LO.

- pokud se napíše instrukce WRITE(``:5,5*2:6), tak se napíše 5x mezera, vypočte se 5*2=10 (2 znaky), pro dorovnání se vypíší 4 mezery před číslo a pak samotné znaky `10`.

- pokud se definuje pole jako array[byte], je to totéž jako array[0..255]

- pokud se zadá Delete(Retezec,-Od,Počet), smažou se znaky 1 až Počet+Od.

- pokud má pole 10 prvků a bez kontrol se adresuje 11., přepisuje (při zápisu do této paměti) se RAM definovaná pod tímto polem (resp. v RAM za ním)!

- jako index do pole se dá použít také už definovaný interval
	TYPE	INDEX = -Min..Max;
	VAR	POLE : ARRAY[INDEX] OF BYTE;  

- tento zápis:
	begin A := B; Write(A); end  
vygeneruje 3 příkazy (=,write a prázdný), zatímco tento:
	begin A := B; Write(A) end  
jen dva (bez prázdného příkazu, je to jako u case kdy za : není nic)

- při rotaci obrázků vypočítat polohy jen 4 rohových pixelů (více méně stačí jen 3!) a další pixely získat jejich vykreslením jako čáry pomocí optimalizovaného algoritmu pro kreslení linek (přírůstky). Sin a Cos brát už z před počítané tabulky!

- test na určitý bit lze udělat pomocí AND a jako maska se použije index bitu (0-7) do tabulky 7 bytů, kde bude až daný bit nastaven (ušetří se SHL, ale je nutné číst z RAM)

- na Pentiu platí, že přístup do stále stejného bloku paměti je stejně rychlý jako přístup do registrů (nebo alespoň téměř stejně rychlý). Za předpokladu cache.

- vždy je vhodné (alespoň na Pentiu, jakožto průměrném systému, na který se hra cílí) otestovat rychlost některé (klíčové, např. zobrazování do VRAM, třeba rozdíl mezi MMX a MOVSD) části kódu; nejjednodušeji to půjde takto:
	VAR	POČET : LONGINT;
		CAS : LONGINT;
	CONST	DOBA : DWORD = 18*3;	{přibližně 3 vteřiny, při časovači 18.2 Hz}
		{pracovní procedury (pokud jsou) pro testovanou část}
	BEGIN
		WRITELN;
		POČET := 0;
		CAS := MEML[0:$046C];
		REPEAT
			{testovaná část kód}
			INC(POČET);
		UNTIL MEML[0:$046C]-CAS > DOBA;
		WRITELN(POČET);
	END.  

Vhodné je otestovat obě/všechny varianty možného řešení najednou (tj. okopírovat tu část mezi BEGIN a END - tolikrát, kolik je možných variant).


- u polí (dynamických(!), tj. jen array of word;) se přiřazení A := B nebere jako zkopírování B do A, ale jen kopie ukazatelů, tj. cokoliv se od této chvíle zapíše do B, zapíše se i do A. U recordů lze ale A := B použít namísto MOVE.

- u statických polí (musí být definovány buď jako "X,Y : array" nebo "X : Type; Y : Type", jinak budou brány jako nekompatibilní typy) lze provést X := Y a bude se to interpretovat jako MOVE(Y,X,SizeOf(X));. Totéž platí pro RECORDy.

- FP nebude generovat zásobník a Start/Exit kód procedury, pokud je psaná jako Assembler (hned za hlavičkou) a nemá žádné parametry v hlavičce ani lokální (VAR). Bude pak rychlejší.

- udržovat nejčastěji používané proměnné na jednom místě (kvůli cache).

- zjednodušit např.: "If a and (1 shl k) = 1 shl k" na "if a and (1 shl k) <> 0"... je to rychlejší (-SHL, CMP->OR)

- namísto instrukce X MOD Y použít ne postupné odečítání ale instrukci X AND (Y-1). Funguje to ale jen tehdy, pokud je Y mocnina dvou!

- data, která se přenáší pomocí MOVS by měla být zarovnána na 4 (lépe 8) byty minimálně (a to jak zdroj i cíl!)

- u dělení/násobení by se nemělo vyskytnout přetečení (zpomaluje to zřejmě program)

- po instrukci MMX pro násobení je vhodné použít 2 jiné normální MMX instrukce, kvůli párování.

- na Pentiích s MMX používat raději 64 bitové přenosy než 32 bitové, mělo by to být rychlejší

- pokud je nutné číst z nějaké proměnné její 16/32 bitovou část (a je to >BYTE), nesmí se (kvůli rychlosti) zapisovat do její dolní 8 bitové části (pokud je to nutné, nejprve se musí celá proměnná vynulovat, popř. použít pracovní proměnnou stejné délky, tu vymazat, pak naplnit a pak Ornout k té původní, jejíž dolní část se také musí vymazat).

- pro párování je vhodné nepoužívat hned po sobě proměnné, které na sobě závisí (resp. např. na prvním řádku se do proměnné nahrávají data, na druhém se proměnná čte nebo bere jako adresa).

- při náhradě reálných čísel za celočíselné lze klidně použít i DWORD: bude to rychlejší (32 bitové proměnné na 32 bitovém CPU), a i tak ještě o 2 byty menší než typ REAL (a nemusí se používat NPU).

- možno použít otevřené parametry, tj. namísto FUNKCE(VAR NĚCO : BYTE) je možné zadat FUNKCE(VAR NĚCO) a za něco je pak možné dosadit cokoliv (byte, word, longint). Totéž je možné s polem: např. FUNKCE(VAR POLE : ARRAY OF WORD). VAR není povinný (u polí je to ale lepší, uloží se jen adresa a ne celá kopie pole!). Pomocí SizeOf se dá zjistit, jak je pole/operand velký (toto používá např. BLOCKREAD), pomocí HIGH a LOW se dá zjistit i rozsah (min a max) prvků. U dynamických polí se dá pole vytvořit libovolně velké kdykoliv na haldě, avšak musí začínat od 0. Díky možnosti (FP) definovat proceduru A(VAR NĚCO) a hned za to ještě A(POLE : ARRAY) je možné udělat i univerzální BlockRead s bufferováním přes RAM. Možno také předat i procedurální typy nebo typ soubor.

- jednou otevřený soubor raději už nezavírat, vždy se snažit vše přečíst při prvním průchodu

- pokud je nutné soubor uzavřít, při opětovném otevírání opět vše zkontrolovat (nespoléhat na to, že je ještě stále na disku a je v pořádku od minule)

- psát IF/THEN/ELSE tak, aby se část za THEN vykonala více častěji (tedy pokud možno většinou)

- občas raději při uvolnění dynamické paměti přes ukazatel dát do ukazatele hodnotu NIL

- namísto porovnávacích operátorů <>= používat množiny a pokud možno s maximálně 32 prvky (hlavně v FP)!

- namísto pole typu BOOLEAN použít množiny (kde to jde, hlavně v testech na přítomnost prvku), je to rychlejší!

- příkaz GOTO použít jen pro vyskočení z více cyklů, jinak nahradit pomocí BREAK, EXIT a CONTINUE

- v cyklech nepřevádět zbytečně písmena na čísla, pokud to není potřeba (hlavně ve FOR).

- proměnné typu pole (ARRAY) předávat do funkcí jen přes VAR (předá se adresa, v opačném případě se zkopíruje celé pole na zásobník!!!)

- nevolat IF...THEN pro nastavení TRUE/FALSE, ale rovnou PODMÍNKA := NĚCO = X;

- nevolat IF NĚCO = TRUE, ale rovnou IF NĚCO THEN - je to rychlejší...

- test VGA/EGA karty: z CMOSu zjistit, zda je EGA/VGA/nic podle bytu a poté pomocí funkce INT 10h "zjisti aktivní adaptér" ověřit, zda je o VGA.

- nepoužívat INTy pro myš, síť, grafiku (kde to jde), ale volat CALL FAR

- netestovat stále dokola myš, ale nastavit si uživatelský obslužný program, který bude vyvolán jen tehdy, pokud se s myší "něco stane". Ovladač to ale musí podporovat!

- před uložením nějaké informace do paměti (např. je aktivní) nejprve otestovat, zda už takovou hodnotu nemá (ušetří se případně zápis do RAM)

- je-li potřeba dělat opakující se cykl (např. mazání bufferu zvukové karty), kde se index pohybuje pouze v mezích 0-3 (4 části), není nutné to dělat pomocí INC a pak testovat pomocí IF a když je větší než 3, tak ho dát 0. Stačí index měnit takto: INDEX := (INDEX + 1) AND 3.

- X MOD 300 není totéž jako X AND 299, to platí jen pro binární počty a jen tehdy, je-li dělitel násobek dvou, a tedy bude možné do AND dosadit namísto jeho nul samé jedničky!

- alokace dynamické konvenční RAM pomocí DOSu (zvláště po malých částech) je velmi pomalé

- pokud se nahradí MOV AX,0 instrukcí XOR AX,AX je nutné počítat s tím, že XOR mění CF bit!

- tabulky SIN a COS spočítat při startu programu ((D)WORD na každý úhel)

- náhodná čísla číst z portu $40 (nastavit časovač na portu $43)

- rychlé vykreslení kružnice:
	d,cx,cy = pracovní integery. x,y,r = parametry pro funkci
     d := 3-(r+r);
     cx := 0;
     cy := r;
     while cx <= cy do
      begin
       p^[y+cy,x+cx]:=c;
       p^[y-cy,x+cx]:=c;
       p^[y+cy,x-cx]:=c;
       p^[y-cy,x-cx]:=c;
       p^[y+cx,x+cy]:=c;
       p^[y-cx,x+cy]:=c;
       p^[y+cx,x-cy]:=c;
       p^[y-cx,x-cy]:=c;
       inc(cx);
       if d < 0 then
        begin
         d := d+cx shl 2+6;
        end
         else
          begin
           d := d+(cx-cy) shl 2+10;
           dec(cy);
          end;
      end;	  
- rychlé vykreslení kružnice lze po úpravě použít i na elipsu (tam se ale musí počítat 2x tolik bodů, protože je souměrná jen podle 2 os a ne podle 4 - resp. 2 os XY a jejich os souměrností).

- ve Windows XP a programech psaných v Turbo Pascalu (ne ve Free Pascalu) může (asi) způsobovat problémy instrukce Rewrite. Pokud daný soubor už existuje, Windows jej nepřepíší na prázdný jako v DOSu nebo ve Windows 9X, ale zřejmě ponechají, a navíc poškodí handle standardního výstupu. Nejlepší metoda je ověřit, zda soubor existuje pomocí RESET a pokud ano, nejprve ho zavřít (CLOSE) a pak smazat (ERASE). Poté teprve použít REWRITE. Je to pomalé, ale program TP7 většinou v XP nepůjde z jiného důvodu, takže je později psán ve FP, takže není nutné se s tímto trápit.

- při inverzi celého obrázku používat NOT EAX (4 byty najednou) a to i pro 3 BPP (udělá se i část dalšího pixelu)

- při zpracovávání (změna amplitudy) zvukových vzorků:
	8 bitové:	0-127 je záporná vlna (tj. -128 až -1)
			128 je 0
			129-255 je kladná vlna (tj. 1 až 127)
	16 bitové:	uloženo jako dvojkový doplněk:
				kladná čísla zpracovat (dělit/násobit) normálně
				záporná čísla (MSB=1)
					nejprve -1 a pak NOT
					zpracovat (SHR, SHL)
					NOT a +1
	(při mixování (sčítání) vzorků není potřeba žádná úprava!)
- snížení hlasitosti zvuků na 1 (pro mix. až 4 zvuků najednou)
	8 bitové:	(VZOREK SHR 2)-32
	16 bit:		je-li MSB=0, pak (VZOREK SHR 2)
			je-li MSB=1, pak NOT(NOT(VZOREK-1) SHR 2)+1  
- použití režimu dvou monitorů
a) pokud je v systému karta VGA a Hercules/MDA, lze na primární kartě VGA pracovat plně stejně jako v jedno monitorovém režimu. Její adresy zůstávají $a000 pro grafiku, $b800 pro text, všechny porty i INT 10h, LFB. Karta Hercules/MDA používá vlastní porty a její grafická paměť v textovém režimu (hned po startu) leží na $b000. Lze tam tedy normálně psát jako do VGA karty (jen je nutné změnit atributy znaků, neboť MDA je černobílé zařízení). MDA/Hercules je možná také nutné nejprve inicializovat...viz. bod C.
b) Pokud jsou v systému dvě karty VGA, každá z nich obsazuje jiné porty. První začíná na portu $3C0 a druhá na $2C0 (viz. ATHelp). Primární z nich obsadí INT 10h, sekundární se musí obsluhovat přes porty (resp. po startu pojede v textovém režimu 3h, kde lze využít paměť $b800, zatímco primární grafická použije $a000). Větší pravděpodobnost je ale v tom, že pouze jedna z těchto karet se stane kartou VGA (to je primární). Ta druhá se v nejhorším případě nenajde vůbec nebo se z ní stane CGA (což většinou způsobí, že primární karta naběhne jako MDA v černobílém provedení!). To záleží na výrobci, nastavení switchů na kartě a také na čipu; ale hlavně na nastavení můstků a přepínačů DIL na kartě, kde se nastavuje nejen, která karta je primární, ale také např. adresa karty EGA (výběr mezi $2xx a $3xx). V ideálním případě se ze sekundární karty stane MDA (zjistíme to např. přes MSD v DOSu nebo na INT 11h, či na INT 10h pro aktivní a neaktivní adaptéry - funkce 1Ah). CGA (někde se píše i MDA, což je asi chybná informace) karta obsazuje standardně porty 3d4,3d5,3d8,3da-3dc (sekundární umí být jen EGA/VGA, tam je na portech $2xx). Je nutné s ní tedy pracovat jako s kartou CGA. Např. režim se nastaví v $xD8. I TP7 rozezná dvě karty (avšak nemusí se mu podařit tu druhou inicializovat). Ale MDA karta obsazuje standardně porty $3B0 a výše (Hercules se např. nastavuje na grafiku na $3B8, MDA ale ne, protože má jen textový režim 7. Nastavit se může také přes CRTC nebo přes port $3C0 (ATC) a jedním z jeho registrů (viz. PORTY.TXT). Pro lepší přehled, viz. PORTS.TXT ve Výuce v TP7...
c) Pokud je v systému primární karta AGP a sekundární PCI, je nutné naprogramovat North Bridge (každý chipset se bohužel nastavuje jinak, viz. dokumentace na internetu, nebo na fóru ). Zde je nutné zapnout bit MDA, který způsobí, že karta AGP se bude chovat stejně jako dosud, ale NB automaticky přesměruje všechny požadavky z 64kB segmentu od $b000 a portů MDA na sekundární PCI kartu. Ta se bude chovat jako barevná MDA, tedy se bude programovat v režimu $3 stejně jako VGA, jen na jiné adrese. Primární AGP se pak chová stejně, jako kdyby byla v systému sama (aplikace, která počítala jen s jedním monitorem, nepozná nic zvláštního). Nebo je nutné použít např. COM ovladač třetí strany, viz. www.asm32.com .
d) Dvě PCI karty je možné používat současně jen tehdy, když se vždy pro tu aktivní přepíná buffer, což je velmi složité a může to u ostatních aplikací způsobovat problémy. Pravděpodbně je nutné je přes příkazy PCI sběrnice nastavit tak, aby každá z karet používala jiný Linear Frame Buffer a do něj pak zapisovat. Zřejmě bude také nutné přemapovat jejich porty.

- dost často platí, že menší program (EXE) obsahuje méně instrukcí a tedy je rychlejší...

- podmínku IF NĚCO = TRUE THEN INC(X,1) nahradit tímto INC(X,ORD(NĚCO)), ovšem pouze, pokud Ord(True)=1 (většinou v Pascalu ano). Ušetří se CMP a JMP, navíc Pentium nemusí předpovídat skoky.

- na novějších kartách podporovat 8 bitovou paletu namísto 6 bitové v režimu 256 barev

- na VESA kartách, které neumožňují měnit paletu přes OUT porty, je nutné použít INTy!

- zvuky se při mixování se mohou ztlumovat. Toto bude probíhat pomocí posunů (bude rozlišení po 12.5%).
	X			100%
	X-X shr 3		87.5%
	X-X shr 2		75%
	X shr 1+X shr 3	62.5%
	X shr 1			50%
	X shr 2+X shr 3	37.5%
	X shr 2			25%
	X shr 3			12.5%  

- efekt ozvěny se udělá tak, že se zvuk bude mixovat několikrát za sebou, přičemž se bude u každého postupně snižovat hlasitost. Čím větší prostor, tím větší mezičas mezi mixováním. V malé místnosti se může zvuk mixovat i do sebe (tj. např. se namixuje jen 1 délky samotného zvuku daleko od jeho začátku znovu, i když tišší).

- efekt pan se dělá u mono zvuku tak, že ten se rozdělí v poměru na levou a pravou část: pokud bude pan 50% (tj. vyjádřeno bytově 64 - 128 je střed) v levo, tak levá část musí hrát 150%, zatímco pravá jen 50% (průměr musí být 100%). Je-li zvuk stereo, tak se ta složka, na jejíž straně je PAN zesílí, ta druhá zeslabí, ale zároveň se daný počet % hlasitosti složky, která se zlumovala, namixuje do levé složky (pokud je PAN 64 (tj. o 25% obrazovky v levo), tak to bude 25% pravé složky, protože ona sama se sníží na 75%). Nutno dávat pozor na přetečení (používat clip "ořezávání").

- Grafika: při zvětšování/zmenšování na jinou velikost než násobek 2x nepoužívat metodu Xnové=Xstaré*poměr, ale metodu, kdy se vypočte počet vynechaných pixelů a jejich pozice (jak sloupce, tak řádky) a pak se přesouvají jen ty bloky dat, jenž neobsahují vynechaný pixel, zároveň se udržuje nový offset v cíli, kam je umístit. Při zvětšování možno nezávisle na videu použít nebo nepoužít antialiasing.

- při vykreslování kružnic dle zadání si pomoci symetrií kružnice a počítat jen 1/8 kružnice a na každý vypočtený bod umístit dalších 7 (do každého ze 4 kvadrantů dva, kružnice je osově souměrná, taktéž je souměrná podle každé osy dvou os X a Y).

- nejpřesnější převod TC obrázku do stupňů šedi se dělá pomocí tohoto vzorce:

I = 0.299*R + 0.587*G + 0.114*B

U funkcí, které provádějí tento převod, by měla být možnost volby, zda při použití DIV 3 metody, tj. bez 4.světlostní složky, se to vypočítá jednoduše (R+B+G)/3 nebo pomocí této pomalejší, ale lépe vystihující odstíny, metody (paleta by měla být vytvořena na 256 odstínů šedi a ne jen na 64, to v případě 8 bitové palety, u 6 bitové bude jen 64 odstínová):

Byte(I) = ((19595*longint(R)+38469*longint(G)+7471*longint(B)) div 3) shr 16)


- funkce pro převod jakéhokoliv obrázku na 2 bitovou (černá a bílá) barvu. Pokud není obrázek ve stupních šedi, nejprve se každý pixel pracovně převede na Gray (tj. pomocí průměru, viz. výše) a pak se na něj aplikuje tento vzoreček:

Cout := 0;

If Cin > Random(255) then Cout := 255;

Cin je vstupní pixel v 8 bitové Gray Scale

Cout je výstupní pixel v 8 bitové Gray Scale

Výsledný obrázek bude mít BPP=1, ale pixely budou mít jen 2 možné hodnoty.

Random vrací <0;254>

Výsledek bude jakoby zrnitá fotografie, jednobarevné plochy se stanou drsnějšími

Pokud se funkce Random(255) nahradí statickou hodnotou 127, bude obrázek převeden tak, že všechny odstíny, co jsou pod 128 budou černé, ostatní bílé.

- metoda rychlého převodu na 256 barev:
při nutnosti zobrazovat obrázek v 256 barvách, je možné kromě standardní metody, která u každé scény zjistí dynamickou paletu a do ní bude pak VVRAM převádět, nebo pro metodu, které se paleta zadá, ale převod zůstává stále stejný (tedy nejbližší barva se vyhledává s postupným nárůstem tolerance), použít třetí. Ta si vytvoří svoji 1 paletu (stačí 1x, není nutné to dělat při nové scéně znovu) a to tímto způsobem:

celá "INC" paleta se musí vejít do 8 bitů (index),

tj. pro jednotlivé složky se vytvoří tyto hodnoty:

R = 3 bity (7-5)

max. 8 hodnot

G = 3 bity (4-2)

max. 8 hodnot

B = 2 bity (1-0)

max. 4 hodnoty

Paleta se vytvoří pouhým zvyšováním indexu o 1 a z něj se vyseparují jednotlivé složky

- ty se ještě musí převést ze svého rozsahu (0-7 nebo 0-3) do rozsahu palety 0-63

(u 8 bitových karet to bude 0-255!; převod pomocí dělení a násobení)

(např. ze 3 bitů na 6: X*8)

Převod jednotlivých barev se provádí pouhým zmenšením počtu bitů na jednotlivé složky:

R3 = R shr 5

G3 = H shr 5

B2 = B shr 6

Při dosažení do vztahu a úpravě z:

Index = R3 shl 5 + G3 shl 2 + B2

Dostaneme tento jednoduchý převod:

I = R and $e0 + (G shr 5) shl 2 + B shr 6

Metoda je nejrychlejší ze všech a u scén, které mají barvy v celém rozsahu palety poskytuje skvělé výsledky. U scén, která má barvy směrem k jednomu konci palety (např. je celá modrá nebo se odehrává v noci) může dojít ke ztrátě detailů, neboť se spousta odstínů soustředí do malého prostoru palety (zatímco u scény ve dne se odstíny rozprostřou po celé paletě). Je to ale jen malá daň za to, že převod TC na 256C nezabere méně času/paměti než převod TC na HC. Pokud se R bere jako nejvyšší prvek, musí se jako první zvyšovat B (do 2 ne-včetně), pak G (do 8) a pak teprve vždy R... Pozor, protože pro modrou jsou jen 4 hodnoty, nebude skoro v obraze vidět... hlavně co se týče složek B < 64. Přesnější, ale pomalejší metoda vycházející z této, je ta, že se navíc ještě zjistí, o kolik hodnot se liší původní složka a ta nová (zaokrouhlená). Pokud se např. složka převádí jako "G8 SHR 5", tak rozdíl se zjistí jako "G8 AND 31" (protože "1 SHL 5" = 32-1 = 31). Pokud je zbytková hodnota větší než 1, přičteme k nové G3 hodnotu "1". Hodnotu pro 1 můžeme také nechat vypočítat náhodně, jinými slovy "IF G8 AND 31 > RANDOM(G8 AND 31) THEN INC G3" (jen pokud už G3 není "7"). O dost to ale zpomalí vykreslování (lepší je to do programu ani nedávat).

2006-11-30 | Martin Lux
Reklamy: