Optimalizace v PDA (Compact Framework)

Za oknem léto. To každej musel zaregistrovat. Není tudíž mnoho času rozšiřovat maxigeniální myšlenky. Radši jezdím, cvičím, jím, spím a tak dále. Teď zpět k dnešnímu tématu. Několikátý měsíc, čistě pracovně, rozšiřuju svoje vědomosti o taje a zákoutí .NETího Compact Frameworku pro PocketPC alias PDA, MDA. O víkendu jsem vyvíjel novou knihovnu pro asynchronní připojení. Ta minulá byla trošku zrezlá. A když bylo dopsáno dostal jsem naprosto geniální nápad.

Vlastní dialog

Ano. Nápad byl vcelku prostý. Do skinovací knihovny přidám podporu pro vlastní dialogové okno. Samozřejmě zcela grafické. Znáte dnešní standard, na něco kliknete, celá obrazovka ztmavne a vyskočí dialog s nějakou fajnovou hláškou. Po odkliknutí to zase všechno odtmavne a jede se dál.

Úkol snadný, ne ovšem na velmi omezené platformě PDA. Tady neexistuje poloprůhlednost. Tady plno věcí neexistuje. První co jsem tedy vymyslel, byla jednoduchá FAKE aplikace, která měla předem připravené obrázky ztmaveného pozadí. Toto řešení má ovšem zásadní problém. Neumožňuje být jakkoliv flexibilní. A taky, pokud budu dialog volat odjinud, než jsem si předkreslil, nebude grafika souhlasit.

Řešení

Po nějakém tom pátrání v dokumentaci jsem pomocí PInvoke sesmolil funkci, která: sejme aktuální obrazovku PDA, uloží do bitmapy. Bitmapu potom pixel po pixelu přepočítá na “tmavé barvy” (prakticky snižuje saturaci a jas) a na tuto bitmapu doprostřed nakreslí obrázek již samotného dialogu – nějaký fancy rámeček s dialogem. Myšlenka geniální, po splácnutí preview dokonce i funkční, no bohužel smrtelně pomalá. Moje představa byla tuto operaci stihnout tak do 50-100ms. Jak dalece jsem se mýlil.


[MORE]

Optimalizace začíná

Nebudu to zdržovat. Celý proces trval neuvěřitelných 10 sekund. Za všechno mohl kód bitmap.GetPixel(x,y). A k němu reverzní SetPixel. Tyto neskutečně neoptimalizované funkce není radno používat. To už jsem věděl z dob programování grafických prvků v C++.

Zapátral jsem a od MVP lidí z MSDN jsem se dozvěděl optimalizační tipy. Základem je použít Bitmap.LockBits a UnlockBits. Tím se dostaneme přímo k nemanagované paměti bitmapy a můžeme začít řádit jak je libo. Výstupem je bajtové pole jednotlivých subpixelů(R,G,B). Pro rozlišení 240×320 je velikost pole nějakých 240*320*3=230400 pixelů. Toliko bodů je třeba nějakým způsobem zanalizovat a přepočítat (jas, kontrast, barevnost atd).

Bláhově jsem říkal, máš vyhráno. Celý proces se zrychlil na neuvěřitelných 180ms. Stačilo tedy napsat funkce na transformaci pixelu a bylo. Napsal jsem tedy vlastní RGB<->HSB transformační funkce. Sem se s nima patlal víc jak den, přetížené, patřičně okomentované, prvotřídní práce. V tuhle dobu jsem ještě netušil, že PDA je potřeba optimalizovat. Když jsem ověřil, že funkce fungují korektně, prostě jsem do smyčky s pixely vložil něco takového

ColorTransform.RGBToHSB(R, G, B, dH, dS, dB)
dB = dB * 0.4
dS = dS * 0.7
ColorTransform.HSBToRGB(dH, dS, dB, R, G, B)

Kód fungoval perfektně, výstupem bylo přesně to co jsem požadoval. Ale ouha. Transformace a násobení každého pixelu sežralo neuvěřitelných 5150ms. A tak jsem začal hledat, proč tomu tak je. Zakomentoval jsem tu část s násobením a hnedka jsem byl o pár sekund rychlejší. Už bylo jasno. Procesor v PDA stěží sčítá, natož násobí 32bit float číslo. A tak jsem přemýšlel dál.

Různá řešení

  • převést pixel na šedou a rozkopírovat do všech kanálů
  • vzít jeden kanál, dělením ztmavit a rozkopírovat do ostatních
  • sečíst všechny kanály a výsledek vydělit nějakým větším číslem
  • počítat jen sudé řádky/sloupce a výsledek rozkopírovat
  • počítat jen nutnou oblast, ostatní vynechat

Každý z těchto nápadů o něco a některé i slušně zrychlily vykonávání, ale stále jsem se pohyboval nad hranicí jedné sekundy a to bylo nepřijatelné. Musí se to počítat v reálném čase. Především výsledky s jedním kanálem vypadaly nadprůměrně hnusně. I to bych překousnul, jenže časy neodpovídaly.

No a tak jsem do noci seděl nad problémem, pořád si prohlížel hodnoty pixelu před ztmavením a po ztmavení a přemýšlel, jak tuhle hodnotu spočítat bez násobení, dělení, sčítaní. Jo a pak mě to samozřejmě trklo. Bitový posun. Není to přesné, nemá to moc možností, ale funguje to pekelně rychle kdekoliv. Tím jsem byl v podstatě hotov. Vzal jsem součet barev jednoho pixelu, shiftnul o 4 a rozkopíroval. 280ms. Super. Výstup cca dobrý.

R = (R + G + B)
R = R >> 4
G = R
B = R

Pak jsem zkusil ještě jednu úpravu a kupodivu, zrychlení a zlepšení výstupu. 255ms

R = R >> 3
G = G >> 3
B = B >> 3

Chybné cesty

Bitový posuv je tak rychlá operace, že i uvažované vynechání (nepočitání) určité oblasti, je pomalejší, než vše nechat přetáhnout posuven. Samotná režie na vyhodnocení, jestli pixel je v oblasti nebo není, je ohromná. To jsem dokonce procházení pole přepsal s doporučenými Int32 jakožto nejrychlejší možný datový typ. Počítání X a Y se samozřejmě musí provádět už v samotném cyklu nikoliv až pomocným výpočtem uvnitř cyklu. Ta režie je tak veliká, že ani vynechání všech pixelů nestačí na kompenzaci zpomalení. Tady by museli přijít taky nějaký optimalizace. Například rozdělit bitmapu pomocí BitBlt na více menším a ty zpracovávat. Nebo vyhodnocovat pozici pixelu jen občas :-/

Přikládám nějaký obrázky, jako ukázku, co je možné a za jakou dobu získat optimalizací. Rozhodně je při takových masových akcích dobře přemýšlet co a jak. Vyplatí se to. V mém případě nejde o přesné zobrazení, oko je blbý, takže je to v pohodě. Nedovedu si představit rychlost při tranformacích s maticí. Tady prostě neplatí běžná pravidla. A teď jsem dostal nápad na parádní prohlížeč obrázků :)

Reálné nasazení

Po doplácání algoritmu do mojí grafické knihovny, jsem vyzkoušel některý scénáře použití a ukázalo se to neskutečně nepoužitelný. Načtení dat z XML, vytvoření prvků, screenshot, transformace a vykreslení, to všechno zabírá víc jak sekundu času. A to je na kliknutí velká prodleva. Nakonec jsem to vyřešil vtlačením vrstvy mezi objekty. Tím se starám o cachování a preload dat. Ještě to bude chtít nějakou inteligenci pro preload. Takhle všechno zabírá moc ramky.

Život programátora, první díl

Dneska jsem se rozhodl napsat něco ze zákulisí mého pracovního procesu. Už nějaký ten rok dělám programátora pro britskou společnost. Jméno není dúležité, stejně ji u nás nikdo nezná. Takže si celý dny sedím v kanclu, datluju do kompu a při tom si všímám, že i tahle profese má určité zákonitosti, schválnosti.

[MORE]

#1

Když mám složitý problém, stačí zajít na záchod a řešení vždy vymyslím. Stačí vlastně odejít od kompu a nápady se sypou. Bohužel čím je menší pravděpodobnost, že si řešení poznamenáte, tím lepší řešení přichází.

#2

Těsně než usnu, vždy přijdu na řešení problému a nebo ještě lépe, vynaleznu novou a naprosto geniální funkcionalitu. Samozřejmě, že počítač už neběží a zvedat se taky nechci.

#3

A prozatím poslední objev. Vždy když mám v mozku geniální myšlenku, konstrukci, algoritmus, tak se mi chce děsně srát a prostě musím běžet na mísu. Čím větší myšlenka, tím větší nutkání. A na míse platí pravidlo jedna, takže většinou geniální myšlenka je ještě víc zgeniálněná :) a po příchodu ke kompu téměř nerealizovatelná.

Dodatek

Teď například dělám vlastní XML skin Loader pro Compact Framework v .NETu. Myšlenka byla na začátku jednoduchá. Mohl bych napsat nějakou pomůcku, abych nemusel komponenty naklikávat, ale mohl je dynamicky generovat z definičního souboru. Z toho se vyklubal XML soubor. Potom následovala myšlenka, že by těch souborů mohlo být více – jednotlivé obrazovky v PDA. Z toho už nebylo daleko ke skinování. Od toho rychle přisel nápad na podporu různých rozlišení a natočení podle skinu. To vše musí zaštitovat vlastní logovací a debugovací knihovna. Vracíme se k dynamickým komponentám. Ze začátku to byl obrázek na pozadí. Potom přišel klikací obrázkový buton. Následoval transparentní label, dynamický timer, textbox, klikací area. Vše završilo rozhraní pro registraci eventů pro callbacky při akci. Všechno se muselo promítnout v nastavení XML.

A tak z několikahodinové práce, je bezmála měsíc kvalitního kodérství. Vychrlil jsem užásné množstvé tříd a rozhraní. Celé to samozřejmě není u konce. Nápady stále přicházejí. Je ale jasné, že žádný projekt nevypadá na první (ani druhý, třetí) pohled tak veliký, jak ve skutečnosti je