Subject : Optimize Islemleri Author : pOiSOn Email : poisone@usa.net Irc : irc.ada.net.tr / #scene.tr #manowar #atheist ICQ : 6795916 LastUpdate : 28 Mart 99 Note : DOS hicbir zaman olmez! (olursede bi daha yazarik!!) ** sELAM! bu +kel'de de c ve asm dillerinde performansi arttirabilmek icin yapabileceginiz islemleri anlatiyorum..baslangicta basit islemlerden giderek yazinin sonuna dogru karmasik islemlere dogru yazdim... aslina bakarsaniz pek siraya dikkat etmedim :)...bu bilgilerin bazilari tecrube bazilarda ordan burdan asirdigim bilgiler..umarim guzel seyler yaparsiniz... ** Register tipi compiler'iniz izin veriyor ise sadece procedur veya bloklar icinde kullanacaginiz degiskenleri register olarak belirtin... void main() { register int count1; for(count1=0;count1<100;count1++) { register int count2; for(count2=0;count2<100;count2++) putpixel(count1,count2,count1^count2) } } bu kodda count1 ve count2 degiskenlerini global olarak tanitmak pek mantikli degil, yani count2 degiskeni sadece ilk for dongusunde lazim. bu kodunuzda cok buyuk hiz fark ettirir. ozellikle gecici olarak dongulerde kullanacaginiz degiskenleri normal degisken olarak tanitmayin. nasil mi ? void main() { ... ... // buralarda count degiskeni tanimli degil { // bu blok herhangi if veya for blogu degil sadece blok.. register int count; for(count=0;count<100;count++) ...... ..... } ..... // buralarda count degiskeni tanimli degil } eger count degiskenini register olarak belirtmez isek degisken stack uzerinde yaratilir.. ** Prosedur parametreleri ikinci olarak bazi procedurlere verilen parametreleri de register olarak tanitmak faydalidir. void putpixel(register int X,register int Y,register color) { register unsigned char * offset; offset = (unsigned char *) ( Y * 320 + X + VGA) *offset = color; } yukarda bu degiskenleri register die tanitilmasaydi tum variablar icin stacktan yer ayrilacak carpma vs. islemleri ile bu is cok daha uzun zaman alacakti. simdi her variable icin bir register kullanilacak. kod register kullanilmadan disasm edildiginde .. 005C putpixel_: 005C 56 push esi 005D 57 push edi 005E 55 push ebp 005F 89 E5 mov ebp,esp 0061 81 EC 18 00 00 00 sub esp,0x00000018 0067 89 45 E8 mov -0x18[ebp],eax 006A 89 55 EC mov -0x14[ebp],edx 006D 89 5D F0 mov -0x10[ebp],ebx 0070 89 4D F4 mov -0xc[ebp],ecx 0073 69 55 EC 40 01 00 00 imul edx,-0x14[ebp],0x00000140 007A 03 55 F4 add edx,-0xc[ebp] 007D 8B 45 E8 mov eax,-0x18[ebp] 0080 01 D0 add eax,edx 0082 89 45 FC mov -0x4[ebp],eax 0085 8A 45 F0 mov al,-0x10[ebp] 0088 8B 55 FC mov edx,-0x4[ebp] 008B 88 02 mov [edx],al 008D 89 EC mov esp,ebp 008F 5D pop ebp 0090 5F pop edi 0091 5E pop esi 0092 C3 ret sonuc olarak 56 byte...ek olarak putpixel'i her cagirmanizda stack'e de biseyler yazmak gerektigini unutmayin.. ve register kullanildiginda 0048 putpixel_: 0048 56 push esi 0049 89 C6 mov esi,eax 004B 89 C8 mov eax,ecx 004D 89 D1 mov ecx,edx 004F C1 E2 02 shl edx,0x00000002 0052 01 CA add edx,ecx 0054 C1 E2 06 shl edx,0x00000006 0057 01 D0 add eax,edx 0059 88 1C 06 mov [esi+eax],bl 005C 5E pop esi 005D C3 ret gozukur. sadece 22 byte ve bunu c compileri yazdi... ** Fonksiyonlar Ozellikle for gibi dongulerde kontrol yaparken fonksiyonlari kullanmayin. Nasil mi ? char str[10]; int count; strcpy(str,"test") for(count=0;count> 2 seklinde degiskenlerinizi ayarlayin mesela bu bir flip rutininde kullanirsak void flip(register unsigned int * dest,register unsigned int * source) { _asm { mov eax,source mov ebx,dest mov esi,eax mov edi,ebx mov eax,SCREEN_DWORDLEN mov ecx,eax rep movsd } } goruldugu uzere ekranin byte olarak boyunu her defada hesaplamak yerine sabit olarak almak daha kolay..peki neden SCREEN_DWORDLEN yerine direk olarak 64000 yazmadik ? bunun sebebi baska cozunurlukte bir ekran actigimizda kodumuzda degisiklik yapmaya veya yeniden hesaplamaya (SCREEN_WIDTH * SCREEN_HEIGHT * SCREEN_BYTEPERPIXEL gibi..) gerek kalmayacak. ek olarak burda watcom c'de bulunan pragma aux'la tum procedur'u yazarsak source ve dest degiskenlerini direk olarak esi ve edi register larina alabilirsiz bu ek olarak 4 komuttan bizi kurtarir..pragma aux'suz derlersek biz yazmadik ama derleyiciniz proc'a girerken register'lari stack'e saklar ve proc. dan cikarken geri alir..pragma aux'la bunu da engellemis olursunuz..cunku bu gereksiz bir islem yani proc'dan ciktiktan sonra esi vb. registerlari eski degerlerine getirmeniz bir mana ifade etmez cunku baska yerde kullanilmayacak... void flip(unsigned char * dest,unsigned char * source); #pragma aux flip= \ "mov ecx,SCREEN_DWORDLEN" \ "rep movsd" \ modify [esi edi ecx] \ parm [edi] [esi]; ** Hardware her zaman daha hizlidir.. Donanim olarak yapilan islemler her zaman softwarelerden cok daha hizlidir mesela 3dfx kartlarin hizina su an (1998) (yil verelimde ehueh) software olarak ulasmak imkansiz.. biz hardware'i nasil kullanabiliriz ? mesela platform oyunlarinda gorulen kocaman ekranin o hizda nasil kaydirildigi... bunu yapmanin bi kac yolu var direk olarak ekran kartinin portlari ile yapilabildigi gibi (bunu pek sevmez siniz.. :) ) artik bir standart olan vesa ile de cok rahat olarak yapabilir siniz... yapacaginiz tek sey ekran kartinin LFB adresine resmi cizmek ve ekran boyunuzu ayarladiktan sonra ekranin 0,0 koordinatinin yerini degistirmek register count1,count2; // 320x200x32 bitlik ekran aç.. Vesa_OpenGraph(320,200,32) // LFB'yi doldur (ekraný deðil!!) for(count1=0;count1<800;count1++) for(coun2=0;count2<600;count2++) putpixel(count1,count2,count1^count2,LFB) // ^^^ pixelin nereye basilacagi // ekran uzunlugunu ayarla Vesa_SetDisplayWidth(320); // ekran uzunluðu artýk 320 pixel.. while(inp(0x60) != 1) { // ekran koordinatlarýný hesapla DoPan(); // ekran koordinatlarini degistir Vesa_SetDisplayStart(Screen_X,Screen_Y); } bu kod ile ekranin scroll hizi mukemmel seviyeye gelir.. :)) ** Compiler'in ipleri elinizde olsun her zaman yazdiginiz bir rutin c'de gozuktugu gibi hizli olmayabilir.. bunu kontrol etmenin tek yolu kodunuzu asm'ye cevirip neler olup bittigini kontrol etmeniz..unutmamak gereken sey c'de az komut kullanmaniz hizli calisagi manasinda degil..bunu nasil kontrol edebilirim diyorsaniz TC'de derlerken (tam emin degilim cunku bayaa bi zaman oldu) -S parametresi asm ciktisi veriyor.. watcom'da ise herhangi bir obj dosyayi asm'ye ceviren wdis.exe yi kullanabilirsiniz..( c'de elinize bir silah alirsiniz...bununla ne yapacaginiz size baglidir,asm'de elinizdeki silahi ayaginiza ates edersiniz.. :) ehuhe.. buda cok klasik oldu :) ..) ** Lookuplar... Lookuplar ornek olarak matematik islemlerinde cok kullandiginiz islemlerin sonuclarini (mesela Y*320) bir tabloya yazarak o tablodan erismek verilebilir short int Sine[360]; short int Cose[360]; void InitSinCos() { register count; for(count=0;count<=360;count++) { Sine[count] = (int) ((float) sin(count) * 32768); Cose[count] = (int) ((float) cos(count) * 32768); } } void main() { register count; for(count=0;count<=360;count++) { printf("Sin %d: %f Cos %d: %f\n",count,((float)Sine[count]/32768),count,((float)Cose[count]/32768)) } } boylece her sinus degerine ihtiyacimiz oldugunda tablodan almis oluyoruz eger sinus degeri ile daha karmasik islemler yapcaksaniz once buyuk sayilarla islemleri yapin daha sonra >> 15 kullanarak 32768'e bolmek cok hizli bir sonuc verecektir..ama size tavsiyem bu degerleri bir dosyaya yazip kodunuza bir sabit dizisi seklinde vermeniz daha iyi cunku matematik kutuphanesini include etmeniz gerekmez.. sinus degerlerinde daha fazla kararliklik istiyorsaniz dizi tipini int tanimladiktan sonra 2.147.483.648 ile carparak istemediginiz kadar hassasiyet elde edebilirsiniz.. :)) ayni sekilde bu teknigi putpixel icinde yapabilirsiniz... 320x200 modu icin.. int YValues[200]; void InitYValues() { register count; for(count=0;count<200;count++) YValues[count] = count * 320; } YValues[120] artik 120 * 320'ye esit oluyor.. boylece her putpixel cagirilinda Y degeri ile 320'yi carpmak yerine direk diziden y degerini vererek carpim sonucunu bulabilirsiniz. ** PipeLine'i kullanin... pipeline ??! pipeline basit olarak anlatmak gerekirse, bazi 486'larda ve pentium serisi islemcilerde kullanilan bir teknolojidir.. bu teknolojiyi soyle anlatmak lazim.. bir su isitacagi dusunun... su isitilan bir borunun icerisinden geciyor.. eger biz bu borularin adedini arttirir isek ve suyu borulardan gectikten sonra yani isindiktan sonra tekrar daha buyuk bir boruda birlestirirsek ayni anda daha cok sicak su elde etmis oluruz. ayni yontem cpu'da kullaniliyor komutlar cpu'ya girmeden once bir filitreden geciyor ve bu filtrede bi kac parcaya bolunuyor..boylece islenen komut bitmeden bir sonraki komutta filitreye giriyor ve ayni anda birden fazla komut isleme girmis oluyor. fakat burda dikkat edil mesi gereken komut bir sonraki komutu etkiliyor ise sonraki komutun bekletil mesi bu yuzden mumkun oldugunca sonraki komutlari etkilemeyen kod yazmaniz.. peki biz bu teknolojiyi nasil kullanabiliriz dersek bunu zaten optimized compilerlar sizin icin yapiyor fakat sizin yazdiginiz asm kodlari her zaman optimize edilmiyor bunun icin asm ile yazdiginiz kodlarda mumkun oldugunca bir sonraki komutta degeri degismeyen sirada yerlestirin komutlari.. nasil mi ? biraz onceki kodda .. mov eax,source ;; source -> ax mov esi,eax ;; ax'in source degeri almasi beklenir mov ebx,dest ;; burada bekleme olmaz pipeline'a girer mov edi,ebx ;; ebx'in dest degeri almasi beklenir mov eax,SCREEN_DWORDLEN ;; pipeline'a girer mov ecx,eax ;; eax'in degerini almasi beklenir rep movsd ;; yazarsak, mov eax,source isleme girecek ve eax'degeri degistigi icin mov esi,eax komutu sirada bekletilecektir.. ve mov ebx,dest isleme girdiginde mov edi,ebx komutu yine bekletilecektir.. cunku ebx'e yeni degerin yuklenmesi beklenecektir. bunun yerine komutlarin sirasini soyle degistirirsek mov eax,source mov ebx,dest mov esi,eax mov edi,ebx mov eax,SCREEN_DWORDLEN mov ecx,eax rep movsd yazarsak, eax'e source getirilene kadar mov ebx,dest komutuda isleme girecektir.. ve ebx'e dest degeri yukleninceye kadarda mov esi,eax komutu siraya girecektir..boylece ayni anda ikiser komut isletmis oluyoruz. ascizz string kopyalama islemi... ÚÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÂÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄ¿ ³ 8088³ 286³ 386³ 486³ 586³586Pipe³byte³ loop2: ÃÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÅÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄ´ lodsb ³ 16³ 5³ 5³ 5³ 2³ 2³ 1³ stosb ³ 15³ 3³ 4³ 5³ 3³ 3³ 1³ or al,al ³ 3³ 2³ 2³ 1³ 1³ 1³ 2³ jne loop1 ³ 16³ 8³ 8³ 3³ 1³ 0³ 2³ ÃÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÅÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄ´ ³ 50³ 18³ 19³ 14³ 7³ 6³ 6³ ÀÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÁÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÙ ÚÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÂÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄ¿ ³ 8088³ 286³ 386³ 486³ 586³586Pipe³byte³ loop2: ÃÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÅÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄ´ mov al,[si] ³ 17³ 5³ 4³ 1³ 1³ 1³ 2³ inc si ³ 3³ 2³ 2³ 1³ 1³ 0³ 1³ mov [di],al ³ 18³ 4³ 2³ 1³ 1³ 1³ 2³ inc di ³ 3³ 2³ 2³ 1³ 1³ 0³ 1³ cmp al,0 ³ 4³ 3³ 2³ 1³ 1³ 1³ 2³ jne loop2 ³ 16³ 9³ 8³ 3³ 1³ 0³ 2³ ÃÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÄÅÄÄÄÄÅÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄ´ ³ 61³ 25³ 20³ 8³ 6³ 3³ 11³ ÀÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÁÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÙ goruldugu gibi komut sayisi artmasina ragmen pipeline sayesinde islem daha kisa suruyor... ** Assembler optimizasyonlari... yeterli assembler bilginiz varsa yazdiginiz rutinlere bazen %40-%60 (bazen %90 >:) ) arasi performans kazandirabilirsiniz... daha once verdigim putpixel prosedurunde biraz calisirsak... ilk rutin boyle idi.. 0048 putpixel_: 0048 56 push esi [1] 0049 89 C6 mov esi,eax [1] 004B 89 C8 mov eax,ecx [1] 004D 89 D1 mov ecx,edx [1] 004F C1 E2 02 shl edx,0x00000002 [1] 0052 01 CA add edx,ecx [1] 0054 C1 E2 06 shl edx,0x00000006 [1] 0057 01 D0 add eax,edx [1] 0059 88 1C 06 mov [esi+eax],bl [1] 005C 5E pop esi [1] 005D C3 ret [4] toplam: 14 clock bu isi yapmanin daha kisa yolu soyle eax'de color , edi'de y , ebx'de x koordinatinin gonderildigini varsayalim.. shl edi,6 ;; y = y * 64 [1] lea edi,[edi*4+edi] ;; y = y * 5 [1] ;; edi sonuc olarak y = y * 320 oldu mov [edi+ebx+VGA],al ;; y + x + offset adresine al'yi at.. [1] sonuc olarak putpixel'i 3 clock'a indirmis olduk... :) eger lookup table kullanirsak.. int YTable[200]; ... ... mov edi,[edi*4+table] ;; y = y * 320 [1] mov [edi+ebx+VGA],al ;; y + x + offset <- al [1] artik 2 clock.. bu is bu kadar... bazi asm kodlarini daha hizli yontemleri... add == lea reg,[reg+reg+n] add == lea reg,[reg+reg*n+m] dec reg == lea reg,[reg-1] inc reg == lea reg,[reg+1] div 2^n == shl reg,n lodsX komutlari 286-386 islemciler icin hizlidir... 486lardan sonra mov komutlari daha hizlidir... lodsb == mov al,[si] inc si lodsw == mov ax,[si] add si,2 lodsd == mov eax,[esi] add esi,4 loop komutu 286'lar icin hizli... dec cx / jnz ikilisi sadece 1 clock ta islenir... loop