Úvod
Optimalizace kódu je důležitou součástí vývoje aplikace. Správný kód by měl být napsán co nejefektivněji, což se potom ve výsledku projeví ve výkonnosti celé aplikace. Tento článek by vám měl pomoct při optimalizaci těch nejzákladnějších částí kódu, které se právě díky nevhodnému použití stávají brzdou celé aplikace.
Hodnotové typy a referenční typy
Data hodnotových typů (základní datové typy a struktury) jsou izolovaně udržována v paměti (v zásobníku - Stack), to znamená že více proměnných se nemůže odkazovat na stejnou hodnotu. Referenční typy jsou přesný opak - proměnné udržují pouze ukazatel na data v paměti, je tedy možné sdílet jedinou instanci více proměnnými (ukazatele jsou na tzv. hromadě - Heap). Práce se zásobníkem je rychlejší než práce s hromadou, zde je tedy nutné zvážit, zda-li se vyplatí použít hodnotové typy místo referenčních. Obecně je lepší použít hodnotové typy v případě, že nepotřebujete flexibilitu referenčního typu (třídy). Efektivita hodnotových typů však klesá s jejich velikostí. Podle MSDN může volba typu způsobit neznatelný až třicetiprocentní nárůst výkonu.
Early Binding a Late Binding
Jestliže nadeklarujete proměnnou jako specifickou třídu, potom je tzv. Early Bound, což znamená konkrétního datového typu. To umožňuje kompilátoru předem provést optimalizace (ověření datového typu a členů datového typu), které samozřejmě vedou ke zvýšení výkonu. V opačném případě lze proměnnou nadeklarovat jako typ Object, což je sice mimořádně flexibilní, ovšem za cenu snížení výkonu (nelze provést optimalizace). Takováto proměnná se nazývá Late Bound. Proto silně doporučuji vždy mít zapnuto Option Explicit On, což vyžaduje striktní deklaraci datového typu (v opačném případě to bude vždy Object, tak jako ve VB6 to byl vždy Variant).
Velikost datového typu
Nejefektivnější datový typ je takový, jaký je nativní pro danou platformu. Dnes je zatím nejběžnější platforma 32-bitová, proto efektivita celočíselných typů je následující (od nejlepšího k nejhoršímu): Integer, UInteger, Long, ULong, Short, Byte, SByte. U zlomkových čísel je to Double, Single a Decimal. Výkon lze výrazně zvýšit také vypnutím možnosti Remove integer overflow checks na kartě Compile/Advanced Compile Optimizations... ve vlastnostech projektu. Jestliže je tato možnost zapnuta (výchozí), kompilátor pokaždé ověřuje zda-li proměnná typu Integer nepřetekla a v případě přetečení nastane vyjímka. V opačném případě proměnná přeteče bez vyvolání vyjímky.
Boxing a Unboxing
Chcete-li uložit hodnotový typ do referenčního typu (například Integer do proměnné typu Object), musí .NET Runtime provést zapouzdření, tzv. Boxing. To se skládá ze zkopírování hodnoty, uložení hodnoty do nově alokovaného objektu a uložení informací o typu. Z toho plyne, že toto je náročná operace, které je třeba se vyvarovat. Unboxing je opačný postup, kdy z proměnné typu Object získáme konverzí původní Integer. Doporučuji vždy mít zapnuto Option Strict On, což vás bude nutit používat explicitní konverzi (CType, CObj), která sice nezabrání Boxingu, ovšem zamezí nechtěnému přiřazení hodnotového typu do referenčního.
Pole
Vyvarujte se používání vícerozměrných polí. Jednorozměrná pole jsou narozdíl od vícerozměrných optimalizována. Pokud nezbytně potřebujete použít více rozměrů, použijte raději "pole polí", tj. místo values(0, 0) použijte values(0)(0). Rozdíl ve výkonnosti jednorozměrných a vícerozměrných polí může být až 30%. Mimochodem vícerozměrná pole nevyhovují specifikaci CLS (Common Language Specification), takže když už je používáte, měli byste příslušné metody označit atributem CLSCompliant(False).
Kolekce
Když procházíte kolekci, můžete použít buď For nebo For Each. Jestliže budete přidávat, odstraňovat nebo přesouvat jednotlivé položky, je lepší použít For. For vám také umožňuje procházet kolekci pozpátku, což v případě For Each nelze. Kromě toho všechny kolekce odvozené od základní třídy System.Collections.CollectionBase vyvolají vyjímku jestliže v průběhu procházení pomocí For Each změníte obsah kolekce (přidáte nebo odeberete položku). Jestliže chcete do kolekce přidat více položek, snažte se použít metodu AddRange, protože poskytuje vyšší výkon než přidávání jednotlivých položek v cyklu.
Kolekce versus Pole
- Obecně je práce s polem rychlejší než práce s kolekcí, proto když vám stačí metody obsažené ve třídě Array (představuje pole), dejte jí přednost před kolekcí.
- Potřebujete-li přístup pomocí indexu, použijte pole.
- Potřebujete-li přístup pomocí klíče, použijte kolekci (ideálně Dictionary(Of TKey, TValue)).
- Potřebujete-li často vkládat a odstraňovat položky, použijte kolekci.
Vlastnosti, proměnné a konstanty
S proměnnými se pracuje rychleji než s vlastnostmi. Proměnná je jednoduše místo alokované v paměti, kdežto vlastnost jsou ve skutečnosti dvě metody - jedna pro čtení a druhá pro zápis (nebo jen jedna pro čtení, je-li vlastnost pouze pro čtení). Volání metod je samozřejmě pomalejší než přímý přístup na proměnnou. Z hlediska architektury by však třídy neměly obsahovat veřejné proměnné, vždy raději vlastnosti (z hlediska budoucího rozšíření, například validace hodnoty). Nejrychlejší přístup je ke konstantám, což jsou hodnoty napevno zkompilované v kódu (vyjímkou jsou konstanty typu DateTime a Decimal).
Operátory
- Jestliže dělíte celá čísla a nepotřebujete zbytek, použijte operátor pro celočíselné dělení (\), který může být údajně až 10x rychlejší než operátor pro dělení se zbytkem (/).
- Jestliže potřebujete "sečíst" více hodnot typu String, použijte specializovaný operátor pro sjednocování Stringů "&" místo obecného "+", který je v případě sčítání Stringů pomalejší.
Okamžitá inicializace vs. Líná inicializace
Máte-li třídu, která na základě parametrů předaných v konstruktoru vrací nějaké vypočítané hodnoty pomocí svých vlastností a/nebo metod, máte v zásadě dva způsoby jak hodnoty vypočítat. První způsob (Okamžitá inicializace) spočívá ve vypočtení hodnot předem již v konstruktoru třídy. Tento způsob je vhodný v případě, že k vlastnostem/metodám této třídy bude velmi častý přístup. Druhý způsob (Líná inicializace) spočívá ve vypočtení hodnot "na poslední chvíli", to znamená teprve v momentě kdy je to potřeba (přístup k dané vlastnosti/metodě). Tento způsob se vyplatí tam, kde k vlastnostem/metodám třídy není častý přístup.
Sjednocení cyklů
Snažte se minimalizovat počet cyklů sjednocením do jednoho pokud je to možné.
Dim customerNames(99) As String
Dim customerSurnames(99) As String
'Špatně:
For index As Integer = 0 To 99
customerNames(index) = "Jan"
Next
For index As Integer = 0 To 99
customerSurnames(index) = "Novák"
Next
'Správně:
For index As Integer = 0 To 99
customerNames(index) = "Jan"
customerSurnames(index) = "Novák"
Next
Nikdy nepoužívejte náročné datové typy (Single, Double, Decimal) jako index při procházení cyklem!!!
Rozhodování
Při rozhodování pomocí Select Case se snažte nejpravděpodobnější případy umístit na začátek bloku Select Case.
Short-Circuiting (zkratování)
Zkratování je způsob vyhodnocování více výrazů, při kterém lze kontrolu zbývající části podmínky vynechat a tím urychlit průběh programu.
výraz1 výraz2 And AndAlso Or OrElse
True True True True True True*
True False False False True True*
False True False False* True True
False False False False* False False
* výraz2 se nevyhodnocuje
If (sex = "Female") AndAlso (age >= 18) Then
'Pokud pohlaví nebude ženské, věk se ani nebude vyhodnocovat
End If
If (sex = "Male") And (age >= 18) Then
'Věk se bude zbytečně vyhodnocovat i přesto že pohlaví nebude ženské
End If
Přístup k členům třídy
Pokud to jde, používejte klíčové slovo With, čímž se vyvarujete opětovného zbytečného volání.
'Špatně:
Form1.Controls.Item("Button1").Text = "Tlačítko 1"
Form1.Controls.Item("Button1").Enabled = False
'Správně:
With Form1.Controls.Item("Button1")
.Text = "Tlačítko 1"
.Enabled = False
End With
Některé vlastnosti nebo metody mohou vracet nové instance objektů. Jestliže neznáte interní implementaci takové vlastnosti, pište kód vždy tak, jako by vlastnost pokaždé vracela novou instanci objektu. To znamená pokud budete například v cyklu pracovat s touto vlastností, uložte si ji předtím do dočasné proměnné. Pokud by vlastnost skutečně vracela novou instanci při každém přístupu, procházeli byste vlastně při každém průchodu cyklem úplně jiná data a kromě toho by byl kód pomalejší vzhledem k režii na vytvoření nové instance při každém průchodu. V případě vlastnosti musíte ještě připočítat režii na volání metod Get/Set.
'Špatně:
For index As Integer = 0 To 9
Dim name As String = Form1.Controls(index).Name
Next
'Správně:
Dim controls As ControlCollection = Form1.Controls
For index As Integer = 0 To 9
Dim name As String = controls(index).Name
Next
Konverze typů
Pokud mají oba typy které chcete konvertovat mezi sebou vzájemný dědičný vztah, použijte konverzní funkci DirectCast místo CType. DirectCast poskytuje vyšší výkon než CType.
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs)
Dim b1 As Button = DirectCast(sender, Button)
'Třída Button je zděděná od Object, tudíž konverze bude fungovat
End Sub
Vyjímky
Jestliže používáte v kódu blok Try ... Catch ... Finally, měli byste jej používat pouze na ošetřování kritických chyb, protože má nezanedbatelně velkou režii. Nesprávný způsob použítí Try ... Catch ... Finally je řízení toku kódu, zjednodušeně řečeno vyhození vyjímky jen z toho důvodu, aby se skočilo jinam.
'Špatně:
Try
Dim x As Integer = 10
If x <= 10 Then
Throw New Exception()
End If
Debug.WriteLine(x)
Catch
Finally
Debug.WriteLine("Konec")
End Try
'Správně:
Try
Dim x As Integer = 10
If x <= 10 Then Return
Debug.WriteLine(x)
Catch
Finally
Debug.WriteLine("Konec")
End Try
Práce s textovými řetězci
Pokaždé když změníte proměnnou typu String, je v paměti ponechána stávající instance a vytvořena nová. Frekventovanou změnou proměnné typu String tedy zbytečně plníte paměť. Proto existuje třída StringBuilder, která vždy drží jedinou instanci Stringu i po změnách.
'Špatně:
Dim numbers As String
For index As Integer = 0 To 9
numbers &= index.ToString()
Next
'V paměti existuje 10 instancí Stringu
Debug.WriteLine(numbers)
'Správně:
Dim numbers As New StringBuilder(10)
For index As Integer = 0 To 9
numbers.Append(index.ToString())
Next
'V paměti existuje 1 instance Stringu
Debug.WriteLine(numbers.ToString())
'Špatně:
Dim abc As String = String.Empty
abc &= "a"
abc &= "b"
abc &= "c"
'Správně:
Dim abc As String = "a" & "b" & "c"
Při převádění hodnot na typ String používejte metodu ToString, protože je rychlejší než funkce CStr.
Metody
Při psaní metod je třeba zvážit, zda-li se metoda bude používat ještě na dalších místech. S mechanismem volání metody je spojena určitá režie, která se projeví hlavně v cyklech. Máte-li tedy jistotu že kód procedury se nebude používat ještě na jiném místě, bude kód rychlejší napíšete-li ho přímo do těla cyklu.
Kompilace
Jak jistě víte, aplikace se kompiluje do tzv. MSIL kódu, což je platformově nezávislý binární kód (stejně jako Bytecode v Javě), který je kompilován podle potřeby do nativního kódu procesoru během činnosti programu (konkrétně při prvním volání metody). Kompilace podle potřeby (Just-In-Time) samozřejmě má nějakou režii a proto lze celou aplikaci předem předkompilovat do nativního kódu pomocí nástroje Ngen (Native Image Generator), což může výrazně urychlit spuštění aplikace. Na druhou stranu to ovšem může zpomalit některé často volané metody, protože nástroj Ngen neumí provádět takové optimalizace jako JIT kompilátor. Pro více informací:
http://msdn2.microsoft.com/en-us/library/6t9t5wcf(vs.71).aspx
Optimalizace zobrazení uživatelského rozhraní
- Vyvarujte se zbytečného překreslování ovládacích prvků. Zavolejte metodu BeginUpdate před přidáním položek a EndUpdate po přidání položek u komponent typu seznam (ListView, ListBox...)
- Při uživatelském vykreslování komponent se snažte překreslit jen potřebnou oblast, ne celou komponentu což bude mít za výsledek rychlejší odezvu.
- Pokuste se kritická data načíst předem. Bude to sice pomalejší ze začátku, ale další práce s nimi bude potom rychlá.
- Veškeré dlouhotrvající operace přepište na zpracování ve vláknech s možností přerušit danou operaci a ukazatelem stavu.
Minimalizace využití paměti
- Mějte současně načteny jen ty instance formulářů, které zrovna potřebujete.
- Používejte co nejméně ovládacích prvků a tyto ovládací prvky co nejvíce odpovídající dané funkčnosti (například je zbytečné používat TextBox tam, kde je text pouze pro čtení a stačí Label)
- Vyvarujte se použití větších datových typů než potřebujete a to obzvlášť v polích a kolekcích.
- Když dokončíte práci s velkým datovým typem (textové řetězce, pole, kolekce a další potenciálně velké datové typy) nastavte jej na Nothing.
- Používejte blok Using ... End Using u tříd podporujících rozhraní IDisposable, většinou zapouzdřujících Unmanaged objekty.