Tipy a triky pro zvýšení výkonu v aplikacích .NET

 

Emanuel Schanzer
Microsoft Corporation

Dne

Shrnutí: Tento článek je určený pro vývojáře, kteří chtějí vyladit své aplikace pro optimální výkon ve spravovaném světě. Vzorový kód, vysvětlení a pokyny k návrhu jsou určeny pro databázové, model Windows Forms a aplikace ASP a také tipy pro konkrétní jazyk pro Microsoft Visual Basic a Managed C++. (25 vytištěných stránek)

Obsah

Přehled
Tipy k výkonu pro všechny aplikace
Tipy pro přístup k databázi
Tipy k výkonu pro aplikace ASP.NET
Tipy pro přenos a vývoj v jazyce Visual Basic
Tipy pro přenos a vývoj ve spravovaném jazyce C++
Další materiály
Příloha: Náklady na virtuální volání a přidělení

Přehled

Tato kniha white paper je navržená jako reference pro vývojáře, kteří píší aplikace pro .NET a hledají různé způsoby, jak zlepšit výkon. Pokud jste vývojář, který s rozhraním .NET začíná, měli byste znát platformu i jazyk, který si zvolíte. Tento dokument vychází z těchto znalostí a předpokládá, že programátor už ví dost na to, aby program spustil. Pokud do .NET portujete existující aplikaci, je vhodné si před zahájením portu přečíst tento dokument. Některé z těchto tipů jsou užitečné ve fázi návrhu a poskytují informace, o nichž byste měli vědět, než začnete s portem.

Tento dokument je rozdělený do segmentů s tipy uspořádanými podle typu projektu a vývojáře. První sada tipů je povinná pro čtení pro psaní v libovolném jazyce a obsahuje rady, které vám pomůžou s jakýmkoli cílovým jazykem v modulu CLR (Common Language Runtime). Následuje související část s tipy pro ASP. Druhá sada tipů je uspořádaná podle jazyka a zabývá se konkrétními tipy k používání spravovaného jazyka C++ a Microsoft® Visual Basicu®.

Z důvodu omezení plánu musela doba spuštění verze 1 (v1) nejprve cílit na nejširší funkce a později se zabývat optimalizacemi speciálních případů. Výsledkem je několik případů, kdy se výkon stane problémem. Tento dokument se proto věnuje několika tipům, které jsou navrženy tak, aby se tomuto případu vyhnuly. Tyto tipy nebudou v příští verzi (vNext) relevantní, protože tyto případy jsou systematicky identifikovány a optimalizovány. Použudím je na to, jak půjdeme, a je na vás, abyste se rozhodli, jestli to stojí za to.

Tipy k výkonu pro všechny aplikace

Při práci s CLR v libovolném jazyce je potřeba pamatovat na několik tipů. Ty jsou relevantní pro všechny a při řešení problémů s výkonem by měly být první obranou.

Vyvolání méně výjimek

Vyvolání výjimek může být velmi nákladné, proto se ujistěte, že jich moc nevyhazujete. Pomocí nástroje Perfmon můžete zjistit, kolik výjimek vaše aplikace vyvolává. Může vás překvapit, když zjistíte, že některé oblasti aplikace můžou vyvolat více výjimek, než jste čekali. Pro lepší členitost můžete také zkontrolovat číslo výjimky programově pomocí čítačů výkonu.

Nalezení a návrh kódu s velkými výjimkami může mít za následek slušnou výhru výkonu. Mějte na paměti, že to nemá nic společného s bloky try/catch: náklady se účtují pouze při vyvolání skutečné výjimky. Můžete použít libovolný počet bloků try/catch. Bezplatné použití výjimek je místem, kde ztratíte výkon. Měli byste se například držet stranou od použití výjimek pro tok řízení.

Tady je jednoduchý příklad toho, jak nákladné můžou být výjimky: jednoduše projdeme smyčku For , vygenerujeme tisíce nebo výjimky a pak ji ukončíme. Zkuste příkaz throw okomentovat, abyste viděli rozdíl v rychlosti: tyto výjimky mají za následek obrovskou režii.

public static void Main(string[] args){
  int j = 0;
  for(int i = 0; i < 10000; i++){
    try{   
      j = i;
      throw new System.Exception();
    } catch {}
  }
  System.Console.Write(j);
  return;   
}
  • Pozor! Běh může sám o sobě vyvolat výjimky. Například Response.Redirect() vyvolá výjimku ThreadAbort . I když explicitně nevyvoláte výjimky, můžete použít funkce, které ano. Ujistěte se, že zkontrolujete Perfmon, abyste získali skutečný příběh, a ladicí program, aby zkontroloval zdroj.
  • Pro vývojáře v jazyce Visual Basic: Visual Basic ve výchozím nastavení zapne kontrolu int, aby se zajistilo, že věci jako přetečení a dělení nulou vyvolají výjimky. Pokud chcete dosáhnout výkonu, můžete to vypnout.
  • Pokud používáte com, měli byste mít na paměti, že HRESULTS se může vrátit jako výjimky. Ujistěte se, že je pečlivě sledujete.

Volat na houka

Blokové volání je volání funkce, která provádí několik úloh, jako je například metoda, která inicializuje několik polí objektu. To se má zobrazit u chatty volání, která provádějí velmi jednoduché úkoly a vyžadují více volání, aby se věci dokončily (například nastavení každého pole objektu s jiným voláním). Je důležité, abyste místo chatrných volání napříč metodami, kde je režie vyšší než u jednoduchých volání metod uvnitř AppDomain, je důležité provádět nesčetná volání. Volání P/Invoke, interop a vzdálené komunikace mají režii a chcete je používat střídmě. V každém z těchto případů byste se měli pokusit navrhnout aplikaci tak, aby nespoléhala na malá a častá volání, která nesou tolik režijních nákladů.

K přechodu dochází při každém zavolání spravovaného kódu z nespravovaného kódu a naopak. Doba běhu velmi usnadňuje programátorům interop, ale to je za cenu výkonu. Při přechodu je potřeba provést následující kroky:

  • Provádění seřazování dat
  • Oprava konvence volání
  • Ochrana volaných registrů
  • Přepnutí režimu vlákna tak, aby GC neblokoval nespravovaná vlákna
  • Vytvoření rámce zpracování výjimek pro volání do spravovaného kódu
  • Převzetí řízení vlákna (volitelné)

Pokud chcete urychlit čas přechodu, zkuste použít P/Invoke, pokud je to možné. Režie je pouhých 31 pokynů plus náklady na seřaďování, pokud se vyžaduje řazení dat, a v opačném případě pouze 8. Komunikace COM je mnohem dražší a přijímá více než 65 instrukcí.

Zařazování dat není vždy nákladné. Primitivní typy nevyžadují téměř vůbec žádné řazení a třídy s explicitním rozložením jsou také levné. Ke skutečnému zpomalení dochází při překladu dat, například při převodu textu z ASCI na Unicode. Ujistěte se, že data, která se předávají přes spravovanou hranici, se převedou jenom v případě potřeby. Může se stát, že jednoduše tím, že se dohodnete na určitém datovém typu nebo formátu v rámci programu, můžete snížit spoustu režijních nákladů na seřaďování.

Následující typy se nazývají blittable, což znamená, že je lze kopírovat přímo přes spravovanou/nespravovanou hranici bez jakéhokoli zařazování: sbyte, byte, short, ushort, int, uint, long, ulong, float a double. Můžete je předat zdarma, stejně jako ValueTypes a jednorozměrná pole obsahující blittable typy. Podrobné podrobnosti o marshallingu můžete podrobněji prozkoumat v knihovně MSDN. Doporučuji číst pečlivě, pokud trávíte hodně času seřaďováním.

Návrh s použitím hodnotových typů

Používejte jednoduché struktury, když můžete, a když neděláte velké množství boxování a rozbalování. Tady je jednoduchý příklad, který demonstruje rozdíl v rychlosti:

pomocí systému;

namespace ConsoleApplication{

  public struct foo{
    public foo(double arg){ this.y = arg; }
    public double y;
  }
  public class bar{
    public bar(double arg){ this.y = arg; }
    public double y;
  }
  class Class1{
    static void Main(string[] args){
      System.Console.WriteLine("starting struct loop...");
      for(int i = 0; i < 50000000; i++)
      {foo test = new foo(3.14);}
      System.Console.WriteLine("struct loop complete. 
                                starting object loop...");
      for(int i = 0; i < 50000000; i++)
      {bar test2 = new bar(3.14); }
      System.Console.WriteLine("All done");
    }
  }
}

Při spuštění tohoto příkladu uvidíte, že smyčka struktury je řádově rychlejší. Je však důležité dávat pozor na použití ValueTypes, když s nimi zacházíte jako s objekty. To vašemu programu přidá další režijní náklady na boxování a unboxing a může vás nakonec stát víc, než kdybyste uvízli s objekty! Pokud to chcete vidět v akci, upravte výše uvedený kód tak, aby používal pole foos a pruhy. Zjistíte, že výkon je více či méně stejný.

Kompromisy ValueTypes jsou mnohem méně flexibilní než objekty a při nesprávném použití nakonec uškodí výkonu. Musíte být velmi opatrní v tom, kdy a jak je používáte.

Zkuste upravit výše uvedenou ukázku a uložit objekty a pruhy do polí nebo hashovacích tabulek. Uvidíte, jak nárůst rychlosti zmizí, jen s jednou operací boxování a rozbalování.

Pokud se podíváte na přidělení a kolekce uvolňování uvolňování paměti, můžete sledovat, jak moc je to těžké. To lze provést pomocí funkce Perfmon externě nebo čítačů výkonu v kódu.

Projděte si podrobnou diskuzi o valuetypes v tématu Aspekty výkonu Run-Time technologií v rozhraní .NET Framework.

Přidání skupin pomocí AddRange

Pomocí příkazu AddRange můžete přidat celou kolekci, místo aby se jednotlivé položky v kolekci přidávaly iterativním způsobem. Téměř všechny ovládací prvky a kolekce Windows mají metody Add a AddRange a každý z nich je optimalizovaný pro jiný účel. Přidání je užitečné pro přidání jedné položky, zatímco AddRange má určitou režii navíc, ale vyhrává při přidávání více položek. Tady je jen několik tříd, které podporují Add a AddRange:

  • StringCollection, TraceCollection atd.
  • HttpWebRequest
  • Usercontrol
  • Columnheader

Střih pracovní sady

Minimalizujte počet sestavení, které používáte, aby pracovní sada zůstala malá. Pokud načtete celou sestavu jen kvůli použití jedné metody, platíte obrovské náklady za velmi malý užitek. Zjistěte, jestli můžete duplikovat funkce této metody pomocí kódu, který jste už načetli.

Udržovat přehled o pracovní sadě je obtížné a pravděpodobně by to mohlo být předmětem celého dokumentu. Tady je několik tipů, které vám pomůžou:

  • Pracovní sadu můžete sledovat pomocí vadump.exe. To je popsáno v dalším dokumentu white paper, který se zabývá různými nástroji pro spravované prostředí.
  • Podívejte se na nástroj Perfmon nebo čítače výkonu. Můžou vám poskytnout podrobnou zpětnou vazbu týkající se počtu načítáných tříd nebo počtu metod, které se dají použít jako JIT. Můžete získat informace o tom, kolik času strávíte ve zavaděčí nebo jaké procento času provádění stránkováním strávíte.

Použití smyček For pro řetězcovou iteraci – verze 1

Klíčové slovo foreach v jazyce C# umožňuje procházet položky v seznamu, řetězci atd. a provádět operace s každou položkou. Jedná se o velmi výkonný nástroj, protože funguje jako obecný enumerátor u mnoha typů. Kompromisem pro tuto generalizaci je rychlost, a pokud se hodně spoléháte na iteraci řetězců, měli byste místo toho použít smyčku For . Vzhledem k tomu, že řetězce jsou jednoduchá pole znaků, lze je procházet s mnohem menší režií než jiné struktury. Jit je dostatečně inteligentní (v mnoha případech) k optimalizaci kontroly okrajů a dalších věcí uvnitř smyčky For , ale při procházkách foreach to není povoleno. Konečným výsledkem je, že ve verzi 1 je smyčka For na řetězcích až pětkrát rychlejší než při použití příkazu foreach. To se v budoucích verzích změní, ale pro verzi 1 se jedná o jednoznačný způsob, jak zvýšit výkon.

Tady je jednoduchá testovací metoda, která demonstruje rozdíl v rychlosti. Zkuste ho spustit, pak odebrat smyčku For a odkomentovat příkaz foreach . Na mém počítači trvala smyčka For asi sekundu, s příkazem foreach asi 3 sekundy.

public static void Main(string[] args) {
  string s = "monkeys!";
  int dummy = 0;

  System.Text.StringBuilder sb = new System.Text.StringBuilder(s);
  for(int i = 0; i < 1000000; i++)
    sb.Append(s);
  s = sb.ToString();
  //foreach (char c in s) dummy++;
  for (int i = 0; i < 1000000; i++)
    dummy++;
  return;   
  }
}

TradeoffsForeach je mnohem čitelnější a v budoucnu bude stejně rychlý jako smyčka For pro speciální případy, jako jsou řetězce. Pokud není manipulace s řetězci pro vás skutečným výkonem, o něco složitější kód se nemusí vyplatit.

Použití nástroje StringBuilder pro komplexní manipulaci s řetězci

Při úpravě řetězce se za běhu vytvoří nový řetězec a vrátí ho, přičemž původní řetězec bude uvolněn z paměti. Většinou je to rychlý a jednoduchý způsob, jak to udělat, ale když se řetězec opakovaně upravuje, začne to být zátěž pro výkon: všechna tato přidělení se nakonec prodraží. Zde je jednoduchý příklad programu, který připojí řetězec 50 000 krát, následovaný jedním, který používá StringBuilder objektu k úpravě řetězce na místě. Kód StringBuilder je mnohem rychlejší, a pokud je spustíte, je to okamžitě zřejmé.

namespace ConsoleApplication1.Feedback{
  using System;
  
  public class Feedback{
    public Feedback(){
      text = "You have ordered: \n";
    }
    public string text;
    public static int Main(string[] args) {
      Feedback test = new Feedback();
      String str = test.text;
      for(int i=0;i<50000;i++){
        str = str + "blue_toothbrush";
      }
      System.Console.Out.WriteLine("done");
      return 0;
    }
  }
}
namespace ConsoleApplication1.Feedback{
  using System;
  public class Feedback{
    public Feedback(){
      text = "You have ordered: \n";
    }
    public string text;
    public static int Main(string[] args) {
      Feedback test = new Feedback();
      System.Text.StringBuilder SB = 
        new System.Text.StringBuilder(test.text);
      for(int i=0;i<50000;i++){
        SB.Append("blue_toothbrush");
      }
      System.Console.Out.WriteLine("done");
      return 0;
    }
  }
}

Zkuste se podívat na nástroj Perfmon a zjistit, kolik času se ušetřilo bez přidělování tisíců řetězců. Podívejte se na čítač % času v uvolňování paměti v seznamu paměti .NET CLR. Můžete také sledovat počet uložených přidělení a statistiky shromažďování.

Kompromisy= Existuje určitá režie spojená s vytvořením objektu StringBuilder , a to jak v čase, tak v paměti. Na počítači s rychlou pamětí se StringBuilder vyplatí, pokud provádíte asi pět operací. Obecně bych řekl, že 10 nebo více operací s řetězci je odůvodněním režie na každém počítači, dokonce i pomalejším.

Předkompilovat model Windows Forms aplikace

Metody se při prvním použití používají podle potřeby, což znamená, že pokud vaše aplikace během spouštění provede velké množství volání metod, platíte vyšší penále za spuštění. model Windows Forms používat velké množství sdílených knihoven v operačním systému a režie při jejich spouštění může být mnohem vyšší než u jiných typů aplikací. I když ne vždy, předkompilování model Windows Forms aplikací obvykle vede k výhře výkonu. V jiných scénářích je obvykle nejlepší nechat jit, aby se o to postaral, ale pokud jste model Windows Forms vývojář, můžete se na to podívat.

Microsoft umožňuje předkompilovat aplikaci voláním ngen.exe. Spuštění ngen.exe můžete zvolit během instalace nebo před distribucí aplikace. Rozhodně má největší smysl spustit ngen.exe během instalace, protože se můžete ujistit, že aplikace je optimalizovaná pro počítač, na kterém se instaluje. Pokud před odesláním programu spustíte ngen.exe, omezíte optimalizace na ty, které jsou k dispozici na vašem počítači. Abychom vám poskytli představu o tom, jak moc může předkompilování pomoct, spustil jsem na svém počítači neformální test. Níže jsou uvedené časy studeného spuštění aplikace ShowFormComplex, aplikace winforms s přibližně stovkou ovládacích prvků.

Stav kódu Čas
Architektura s JITed

ShowFormComplex JITed

3,4 s
Předkompilované rozhraní, ShowFormComplex JITed 2,5 s
Předkompilované rozhraní, Předkompilováno ShowFormComplex 2,1 sekundy

Každý test se provedl po restartování. Jak můžete vidět, model Windows Forms aplikace používají velké množství metod předem, takže předkompilování výrazně zvládnou výkon.

Použití zubatých polí – verze 1

JIT v1 optimalizuje zubatá pole (jednoduše "pole polí") efektivněji než obdélníková pole a rozdíl je poměrně znatelný. Tady je tabulka znázorňující zvýšení výkonu vyplývající z použití polí se zubatými místo obdélníkových polí v jazyce C# i Visual Basic (vyšší čísla jsou lepší):

  C# Visual Basic 7
Přiřazení (zubaté)

Přiřazení (obdélníkové)

14.16

8.37

12.24

8.62

Neurální síť (zubatá)

Neurální síť (obdélníková)

4.48

3,00

4.58

3.13

Číselné řazení (zubaté)

Číselné řazení (obdélníkové)

4.88

2.05

5.07

2.06

Srovnávací test přiřazení je jednoduchý algoritmus přiřazení, který je přizpůsobený podrobnému průvodci, který najdete v článku Kvantitativní rozhodování pro firmy (Gordon, Pressman a Cohn; Prentice-Hall; z tisku). Test neurální sítě spouští řadu vzorů v malé neurální síti a číselné řazení je samovysvětlovatelné. Dohromady tyto srovnávací testy představují dobrou indikaci skutečného výkonu.

Jak vidíte, použití zubatých polí může vést k poměrně dramatickému zvýšení výkonu. Optimalizace pro zubatá pole budou přidány do budoucích verzí JIT, ale v případě verze 1 si můžete ušetřit spoustu času pomocí polí se zubatými.

Udržovat velikost vyrovnávací paměti vstupně-výstupních operací mezi 4 kB a 8 kB

Pro téměř každou aplikaci vám vyrovnávací paměť mezi 4 kB a 8 kB poskytne maximální výkon. U velmi konkrétních případů můžete dosáhnout zlepšení díky větší vyrovnávací paměti (například načítání velkých obrázků s předvídatelnou velikostí), ale v 99,99 % případů dojde pouze k plýtvání pamětí. Všechny vyrovnávací paměti odvozené z BufferedStream umožňují nastavit velikost na cokoli chcete, ale ve většině případů 4 a 8 vám poskytne nejlepší výkon.

Podívejte se na příležitosti asynchronních vstupně-výstupních operací

Ve výjimečných případech můžete využít výhod asynchronních vstupně-výstupních operací. Jedním z příkladů může být stažení a dekomprimace řady souborů: můžete číst bity z jednoho datového proudu, dekódovat je na procesoru a zapsat je do jiného. Efektivní použití asynchronních vstupně-výstupních operací vyžaduje velké úsilí, a pokud se to neudělá správně, může dojít ke ztrátě výkonu. Výhodou je, že při správném použití vám asynchronní vstupně-výstupní operace můžou poskytnout až desetinásobek výkonu.

Vynikající příklad programu používajícího asynchronní vstupně-výstupní operace je k dispozici v knihovně MSDN.

  • Je potřeba si uvědomit, že asynchronní volání má malou režii na zabezpečení: Při vyvolání asynchronního volání se zachytí stav zabezpečení zásobníku volajícího a přenese do vlákna, které ve skutečnosti provede požadavek. To nemusí být problém, pokud zpětné volání spouští velké množství kódu nebo pokud se asynchronní volání nepoužívají nadměrně.

Tipy pro přístup k databázi

Filozofií ladění přístupu k databázím je používat jenom funkce, které potřebujete, a navrhnout přístup s odpojeným přístupem: vytvořit několik připojení v posloupnosti, a nedržet jedno připojení otevřené po dlouhou dobu. Tuto změnu byste měli vzít v úvahu a navrhnout ji.

Microsoft doporučuje n-vrstvou strategii pro maximální výkon namísto přímého připojení klienta k databázi. Zvažte to jako součást vaší filozofie návrhu, protože mnoho technologií je optimalizovaných tak, aby využívalo více unavených scénářů.

Použití optimálního spravovaného poskytovatele

Místo toho, abyste se spoléhali na obecné příslušenství, zvolte spravovaného poskytovatele správně. Existují spravované zprostředkovatele napsané speciálně pro mnoho různých databází, jako je SQL (System.Data.SqlClient). Pokud použijete obecnější rozhraní, jako je System.Data.Odbc, pokud byste mohli používat specializovanou komponentu, ztratíte výkon při práci s vyšší úrovní nepřímých přenosů. Při použití optimálního poskytovatele můžete také mluvit jiným jazykem: spravovaný klient SQL mluví TDS s databází SQL, což poskytuje výrazné vylepšení oproti obecné oleDbprotocol.

Pokud je to možné, vyberte čtečku dat před sadou dat.

Čtečku dat používejte vždy, když nepotřebujete data vysouvat. To umožňuje rychlé čtení dat, která se dají uložit do mezipaměti, pokud si uživatel přeje. Čtečka je jednoduše bezstavový datový proud, který vám umožní číst data při jejich příchodu a pak je vypustit, aniž by se ukládaly do datové sady pro další navigaci. Přístup k datovým proudům je rychlejší a má menší režii, protože data můžete začít okamžitě používat. Měli byste vyhodnotit, jak často potřebujete stejná data, abyste se rozhodli, jestli pro vás má ukládání do mezipaměti pro navigaci smysl. Tady je malá tabulka demonstrující rozdíl mezi DataReader a DataSet u zprostředkovatelů ODBC i SQL při načítání dat ze serveru (vyšší čísla jsou lepší):

  ADO SQL
DataSet 801 2507
Datareader 1083 4585

Jak vidíte, nejvyššího výkonu dosáhnete při použití optimálního spravovaného poskytovatele společně se čtečkou dat. Pokud nepotřebujete ukládat data do mezipaměti, může vám použití čtečky dat přinést obrovské zvýšení výkonu.

Použití Mscorsvr.dll pro počítače MP

U samostatných aplikací střední vrstvy a serverových aplikací se ujistěte, že mscorsvr se používá pro počítače s více procesory. Nástroj Mscorwks není optimalizovaný pro škálování nebo propustnost, zatímco verze serveru má několik optimalizací, které jí umožňují dobře škálovat, když je k dispozici více procesorů.

Kdykoli je to možné, používejte uložené procedury.

Uložené procedury jsou vysoce optimalizované nástroje, které při efektivním použití vedou k vynikajícímu výkonu. Nastavte uložené procedury pro zpracování vložení, aktualizací a odstranění pomocí datového adaptéru. Uložené procedury nemusí být interpretovány, kompilovány nebo dokonce přenášeny z klienta a snížit jak síťový provoz, tak režii serveru. Nezapomeňte místo CommandType.Text použít CommandType.StoredProcedure.

Buďte opatrní ohledně dynamických připojovacích řetězců

Sdružování připojení je užitečný způsob, jak opakovaně používat připojení pro více požadavků, a neplatit režijní náklady na otevírání a zavření připojení pro jednotlivé požadavky. Provádí se implicitně, ale získáte jeden fond pro každý jedinečný připojovací řetězec. Pokud generujete připojovací řetězce dynamicky, ujistěte se, že jsou řetězce pokaždé stejné, aby došlo ke sdružování. Mějte také na paměti, že pokud dojde k delegování, získáte jeden fond na každého uživatele. Existuje mnoho možností, které můžete nastavit pro fond připojení, a můžete sledovat výkon fondu pomocí nástroje Perfmon, který sleduje věci, jako je doba odezvy, transakce za sekundu atd.

Vypnutí funkcí, které nepoužíváte

Vypněte automatické zařazení transakcí, pokud to není potřeba. U spravovaného poskytovatele SQL se to provádí prostřednictvím připojovacího řetězce:

SqlConnection conn = new SqlConnection(
"Server=mysrv01;
Integrated Security=true;
Enlist=false");

Při vyplňování datové sady datovým adaptérem nezískali informace o primárním klíči, pokud to není nutné (např. nenastavujte MissingSchemaAction.Add with key):

public DataSet SelectSqlSrvRows(DataSet dataset,string connection,string query){
    SqlConnection conn = new SqlConnection(connection);
    SqlDataAdapter adapter = new SqlDataAdapter();
    adapter.SelectCommand = new SqlCommand(query, conn);
    adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
    adapter.Fill(dataset);
    return dataset;
}

Vyhněte se automaticky generovaným příkazům

Při použití datového adaptéru se vyhněte automaticky vygenerovaným příkazům. Ty vyžadují další cesty na server, aby načetly metadata, a poskytují nižší úroveň řízení interakce. Použití automaticky vygenerovaných příkazů je sice pohodlné, ale v aplikacích s kritickým výkonem stojí za to, abyste to udělali sami.

Dávejte pozor na starší verzi ADO – návrh

Mějte na paměti, že když na adaptéru spustíte příkaz nebo výplň volání, vrátí se každý záznam zadaný dotazem.

Pokud jsou kurzory serveru naprosto povinné, lze je implementovat prostřednictvím uložené procedury v t-sql. Vyhněte se tam, kde je to možné, protože implementace založené na kurzoru serveru se moc dobře neškálají.

V případě potřeby implementujte stránkování bezstavovým způsobem bez připojení. K datové sadě můžete přidat další záznamy:

  • Ujistěte se, že jsou k dispozici informace o pk.
  • Podle potřeby změňte příkaz pro výběr datového adaptéru a
  • Volání výplně

Udržujte datové sady štíhlé

Do datové sady vkládejte jenom záznamy, které potřebujete. Mějte na paměti, že datová sada ukládá všechna svá data do paměti a že čím více dat požadujete, tím déle bude trvat přenos po síti.

Používejte sekvenční přístup co nejčastěji

U čtečky dat použijte CommandBehavior.SequentialAccess. To je nezbytné pro práci s datovými typy objektů blob, protože umožňuje čtení dat z drátu v malých blocích. I když můžete pracovat jenom s jednou částí dat najednou, latence při načítání velkého datového typu zmizí. Pokud nepotřebujete pracovat s celým objektem najednou, budete mít pomocí sekvenčního přístupu mnohem lepší výkon.

Tipy k výkonu pro aplikace ASP.NET

Agresivní ukládání do mezipaměti

Při návrhu aplikace pomocí ASP.NET se ujistěte, že návrh sledujete ukládání do mezipaměti. Na serverových verzích operačního systému máte spoustu možností, jak upravit používání mezipamětí na straně serveru a klienta. V asp je několik funkcí a nástrojů, které můžete využít k získání výkonu.

Ukládání výstupu do mezipaměti – ukládá statický výsledek požadavku ASP. Zadané pomocí direktivy <@% OutputCache %> :

  • Doba trvání – v mezipaměti existuje položka času.
  • VaryByParam – liší položky mezipaměti podle parametrů Get/Post
  • VaryByHeader – liší položky mezipaměti podle hlavičky HTTP.
  • VaryByCustom – liší položky mezipaměti podle prohlížeče.
  • Přepište, aby se lišily podle toho, co chcete:
    • Ukládání fragmentů do mezipaměti : Pokud není možné uložit celou stránku (ochrana osobních údajů, přizpůsobení, dynamický obsah), můžete použít ukládání fragmentů do mezipaměti k uložení částí pro pozdější rychlejší načtení.

      a) VaryByControl – mění položky uložené v mezipaměti podle hodnot ovládacího prvku.

    • Rozhraní API mezipaměti – poskytuje velmi jemné členitosti pro ukládání do mezipaměti tím, že uchovává hashtable objektů uložených v mezipaměti v paměti (System.web.UI.caching). Také:

      a) Zahrnuje závislosti (klíč, soubor, čas)

      b) Automaticky vyprší platnost nevyužitých položek

      c) Podporuje zpětná volání

Inteligentní ukládání do mezipaměti vám může poskytnout vynikající výkon a je důležité se zamyslet nad tím, jaký druh ukládání do mezipaměti potřebujete. Představte si složitý web elektronického obchodování s několika statickými stránkami pro přihlášení a pak řadu dynamicky generovaných stránek obsahujících obrázky a text. Pro tyto přihlašovací stránky můžete použít ukládání výstupu do mezipaměti a pak ukládání fragmentů do mezipaměti pro dynamické stránky. Například panel nástrojů může být uložen do mezipaměti jako fragment. Pro ještě lepší výkon byste mohli ukládat do mezipaměti běžně používané obrázky a často používané texty, které se často zobrazují na webu, pomocí rozhraní API mezipaměti. Podrobné informace o ukládání do mezipaměti (s ukázkovým kódem) najdete na webu ASP. NET .

Stav relace používejte jenom v případě, že potřebujete

Jednou z velmi výkonných funkcí ASP.NET je jeho schopnost ukládat stav relace pro uživatele, jako je nákupní košík na webu elektronického obchodování nebo historie prohlížeče. Vzhledem k tomu, že je tato možnost ve výchozím nastavení zapnutá, platíte náklady v paměti, i když ji nepoužíváte. Pokud nepoužíváte stav relace, vypněte ho a ušetřete si režijní náklady přidáním <@% EnabledSessionState = false %> do asp. To obsahuje několik dalších možností, které jsou vysvětleny na webu ASP. NET .

U stránek, které pouze čtou stav relace, můžete zvolit EnabledSessionState=readonly. To s sebou nese menší režii než úplný stav relace čtení a zápisu a je užitečné, když potřebujete jenom část funkcí a nechcete platit za možnosti zápisu.

Stav zobrazení použijte jenom v případě, že potřebujete

Příkladem zobrazení stavu může být dlouhý formulář, který uživatelé musí vyplnit: pokud v prohlížeči kliknou na Zpět a pak se vrátí, zůstane vyplněný. Pokud se tato funkce nepoužívá, tento stav zatěžuje paměť a výkon. Možná největší vyčerpání výkonu je, že při každém načtení stránky se musí přes síť odeslat signál doby odezvy, aby se aktualizovala a ověřila mezipaměť. Vzhledem k tomu, že je ve výchozím nastavení zapnutá, budete muset určit, že nechcete použít stav zobrazení s <@% EnabledViewState = false %>. Měli byste si přečíst další informace o stavu zobrazení na webu ASP. NET , kde se dozvíte o některých dalších možnostech a nastaveních, ke kterým máte přístup.

Vyhněte se STA COM

Byt COM je navržený tak, aby se zabýval vlákny v nespravovaných prostředích. Existují dva druhy apartment com: jednovláknové a vícevláknové. MTA COM je navržen pro zpracování multithreading, zatímco STA COM spoléhá na systém zasílání zpráv serializovat požadavky vlákna. Spravovaný svět je bez vláken a použití modelu COM s jedním vláknem vyžaduje, aby všechna nespravovaná vlákna v podstatě sdílela jedno vlákno pro interoperabilitu. Výsledkem je obrovský dopad na výkon a měli byste se mu vyhnout, kdykoli je to možné. Pokud nemůžete přenést objekt Objekt COM apartmánu do spravovaného světa, použijte pro stránky, které je používají, @%AspCompat = "true" %>.< Podrobnější vysvětlení modelu STA COM naleznete v knihovně MSDN.

Dávková kompilace

Před nasazením velké stránky na web vždy proveďte dávkovou kompilaci. To se dá zahájit provedením jednoho požadavku na stránku pro každý adresář a čekáním na opětovné idly procesoru. To zabrání webovému serveru zabřednout kompilacemi a zároveň se snaží obsloužit stránky.

Odebrání nepotřebných modulů HTTP

V závislosti na použitých funkcích odeberte z kanálu nepoužívané nebo nepotřebné moduly HTTP. Uvolnění přidané paměti a zbytečných cyklů vám může přinést malé zvýšení rychlosti.

Vyhněte se funkci Autoeventwireup

Místo toho, abyste se spoléhali na autoeventwireup, přepište události ze stránky. Například místo zápisu metody Page_Load() zkuste přetěžovat metodu public void OnLoad(). To umožňuje, aby doba běhu nemusela pro každou stránku provést CreateDelegate().

Kódování pomocí ASCII, když nepotřebujete UTF

Ve výchozím nastavení je ASP.NET nakonfigurovaná tak, aby kódovala požadavky a odpovědi jako UTF-8. Pokud vaše aplikace potřebuje jenom ASCII, můžete odstraněním režijních nákladů na UTF vrátit několik cyklů. Mějte na paměti, že to lze provést pouze pro jednotlivé aplikace.

Použití optimální procedury ověřování

Existuje několik různých způsobů, jak ověřit uživatele a některé z nich jsou dražší než ostatní (z důvodu zvýšení nákladů: None, Windows, Forms, Passport). Ujistěte se, že používáte ten nejlevnější, který nejlépe vyhovuje vašim potřebám.

Tipy pro přenos a vývoj v jazyce Visual Basic

Hodně se změnilo z Microsoft® Visual Basicu® 6 na Microsoft® Visual Basic® 7 a mapa výkonu se s ním změnila. Vzhledem k přidaným funkcím a omezením zabezpečení modulu CLR se některé funkce jednoduše nedají spustit tak rychle jako v jazyce Visual Basic 6. Ve skutečnosti existuje několik oblastí, ve kterých je visual Basic 7 nahrazen svým předchůdcem. Naštěstí existují dvě dobré zprávy:

  • K nejhoršímu zpomalení dochází během jednorázových funkcí, jako je například první načtení ovládacího prvku. Náklady tam jsou, ale platíte je jen jednou.
  • Existuje mnoho oblastí, kde je Visual Basic 7 rychlejší a tyto oblasti obvykle leží ve funkcích, které se během běhu opakují. To znamená, že výhoda v průběhu času roste a v několika případech převáží jednorázové náklady.

Většina problémů s výkonem pochází z oblastí, kde doba běhu nepodporuje funkci jazyka Visual Basic 6, a je nutné ji přidat, aby se zachovala funkce v jazyce Visual Basic 7. Práce mimo dobu běhu je pomalejší, takže používání některých funkcí je mnohem nákladnější. Světlou stranou je, že se můžete vyhnout těmto problémům s trochou úsilí. Existují dvě hlavní oblasti, které vyžadují práci na optimalizaci výkonu, a několik jednoduchých vylepšení, které můžete udělat tady a tam. Dohromady vám to může pomoct obejít snížení výkonu a využít funkce, které jsou mnohem rychlejší v jazyce Visual Basic 7.

Zpracování chyb

Prvním problémem je zpracování chyb. V jazyce Visual Basic 7 se to hodně změnilo a s touto změnou souvisejí problémy s výkonem. Logika potřebná k implementaci OnErrorGoto a Resume je v podstatě extrémně nákladná. Navrhuji rychle se podívat na váš kód a zvýraznit všechny oblasti, ve kterých používáte objekt Err , nebo jakýkoli mechanismus zpracování chyb. Teď se podívejte na každou z těchto instancí a zjistěte, jestli je můžete přepsat tak, aby používaly příkaz try/catch. Mnoho vývojářů zjistí, že pro většinu z těchto případů mohou snadno převést na try/catch , a měli by vidět dobré zlepšení výkonu ve svém programu. Obecně platí, že pokud překlad snadno vidíte, udělejte to.

Tady je příklad jednoduchého programu v jazyce Visual Basic, který používá metodu On Error Goto ve srovnání s verzí try/catch .

Sub SubWithError()
On Error Goto SWETrap
  Dim x As Integer
  Dim y As Integer
  x = x / y
SWETrap:  Exit Sub
  End Sub
 
Sub SubWithErrorResumeLabel()
  On Error Goto SWERLTrap
  Dim x As Integer
  Dim y As Integer
  x = x / y 
SWERLTrap:
  Resume SWERLExit
  End Sub
SWERLExit:
  Exit Sub
Sub SubWithError()
  Dim x As Integer
  Dim y As Integer
  Try    x = x / y  Catch    Return  End Try
  End Sub
 
Sub SubWithErrorResumeLabel()
  Dim x As Integer
  Dim y As Integer
  Try
    x = x / y
  Catch
  Goto SWERLExit
  End Try
 
SWERLExit:
  Return
  End Sub

Zvýšení rychlosti je znatelné. Funkce SubWithError() trvá 244 milisekund při použití metody OnErrorGoto a pouze 169 milisekund při použití metody try/catch. Druhá funkce trvá 179 milisekund oproti 164 milisekundám pro optimalizovanou verzi.

Použití počáteční vazby

Druhý problém se zabývá objekty a typecasting. Visual Basic 6 odvádí spoustu práce pod pokličkou, aby podporoval přetypování objektů, a mnoho programátorů o tom ani neví. V jazyce Visual Basic 7 se jedná o oblast, ze které můžete vyždímat hodně výkonu. Při kompilaci použijte počáteční vazbu. Tím kompilátoru sdělíte, že má vložit převod typu se provede pouze v případě, že je to explicitně zmíněno. To má dva hlavní účinky:

  • Podivné chyby se snadněji vystopuje.
  • Eliminují se nepotřebné donucování, což vede k podstatnému zlepšení výkonu.

Pokud použijete objekt, jako by byl jiného typu, Visual Basic tento objekt vytěsní za vás, pokud ho nezadáte. To je užitečné, protože programátor se musí starat o méně kódu. Nevýhodou je, že tyto donucování mohou dělat neočekávané věci a programátor nad nimi nemá žádnou kontrolu.

Existují případy, kdy musíte použít pozdní vazbu, ale ve většině případů, pokud si nejste jistí, můžete vazbu v rané fázi projít. Pro programátory v jazyce Visual Basic 6 to může být ze začátku trochu trapné, protože se musíte starat o typy více než v minulosti. To by mělo být pro nové programátory snadné a lidé, kteří znají Visual Basic 6, ho rychle převezmou.

Zapnout možnost Striktní a explicitní

Se zapnutou funkcí Option Strict se chráníte před neúmyslnými pozdními vazbami a vynucujete vyšší úroveň disciplíny kódování. Seznam omezení současných s Option Strict naleznete v knihovně MSDN. Upozornění spočívá v tom, že všechny zužující typy donucování musí být explicitně zadány. To však samo o sobě může odhalit další části kódu, které odvádí více práce, než jste si dříve mysleli, a může vám to pomoct při zasypnutí některých chyb v procesu.

Option Explicit je méně omezující než Option Strict, ale přesto nutí programátory, aby ve svém kódu poskytli více informací. Konkrétně musíte před použitím deklarovat proměnnou. Tím se odvozování typu přesune z doby spuštění do doby kompilace. Tato vyloučená kontrola pro vás znamená zvýšení výkonu.

Doporučujeme začít s Explicitní možností a pak zapnout Option Strict. To vás ochrání před pohlcenou chybou kompilátoru a umožní vám postupně začít pracovat v přísnějším prostředí. Při použití obou těchto možností zajistíte maximální výkon vaší aplikace.

Použití binárního porovnání pro text

Při porovnávání textu použijte místo porovnání textu binární porovnání. V době běhu je režie pro binární soubor mnohem lehčí.

Minimalizace použití funkce Format()

Pokud je to možné, použijte místo format() příkaz toString(). Ve většině případů vám poskytne funkce, které potřebujete, s mnohem menší režií.

Použití Charw

Místo znaku použijte znak charw. CLR používá interně Unicode, a pokud se používá, musí se znak přeložit za běhu. To může vést k podstatné ztrátě výkonu a určení, že vaše znaky jsou celé slovo dlouhé (použití znaku charw) eliminuje tento převod.

Optimalizace přiřazení

Místo výrazu exp = exp + val použijte exp += val. Vzhledem k tomu , že exp může být libovolně složité, může to mít za následek spoustu zbytečné práce. To vynutí JIT vyhodnotit obě kopie exp, a často to není potřeba. První příkaz může být optimalizován mnohem lépe než druhý, protože JIT se může vyhnout vyhodnocení exp dvakrát.

Vyhněte se zbytečnému zprostředkování

Při použití byRef předáváte ukazatele místo skutečného objektu. Často to dává smysl (například vedlejší funkce), ale ne vždy to potřebujete. Předání ukazatelů vede k většímu nepřímému přístupu, což je pomalejší než přístup k hodnotě, která je v zásobníku. Když nepotřebujete projít haldou, je nejlepší se jí vyhnout.

Vložení zřetězení do jednoho výrazu

Pokud máte více zřetězení na více řádcích, zkuste je všechny nalepit na jeden výraz. Kompilátor může optimalizovat úpravou řetězce na místě, což zajistí rychlost a zvýšení paměti. Pokud jsou příkazy rozděleny do více řádků, kompilátor jazyka Visual Basic nebude generovat jazyk MSIL (Microsoft Intermediate Language), aby umožnil místní zřetězení. Viz příklad StringBuilder probíraný dříve.

Zahrnout návratové příkazy

Visual Basic umožňuje funkci vrátit hodnotu bez použití příkazu return . Visual Basic 7 to sice podporuje, ale explicitní použití returnu umožňuje JIT provádět o něco více optimalizací. Bez příkazu return je každé funkci přiděleno několik místních proměnných v zásobníku, které transparentně podporují vracení hodnot bez klíčového slova. Když je necháte dál, ztížíte optimalizaci JIT a může to ovlivnit výkon kódu. Prohlédněte si své funkce a vložte return podle potřeby. Nemění to vůbec sémantiku kódu a může vám to pomoct získat z vaší aplikace větší rychlost.

Tipy pro přenos a vývoj ve spravovaném jazyce C++

Microsoft cílí na spravovanou C++ (MC++) na konkrétní sadu vývojářů. MC++ není nejlepší nástroj pro každou úlohu. Po přečtení tohoto dokumentu se můžete rozhodnout, že C++ není nejlepší nástroj a že náklady na kompromisy nestojí za výhody. Pokud si nejste jistí s MC++, existuje mnoho vhodných zdrojů, které vám pomůžou při rozhodování. Tato část je určená vývojářům, kteří se již rozhodli, že chtějí mc++ nějakým způsobem používat a chtějí vědět o jeho výkonnostních aspektech.

Pro vývojáře v jazyce C++ vyžaduje práce se spravovaným jazykem C++ několik rozhodnutí. Chcete přenést nějaký starý kód? Pokud ano, chcete přesunout celou věc do spravovaného prostoru, nebo místo toho plánujete implementovat obálku? Pro účely této diskuze se zaměřím na možnost "port-vše" nebo se budu zabývat psaním MC++ od nuly, protože to jsou scénáře, ve kterých programátor zaznamená rozdíl ve výkonu.

Výhody spravovaného světa

Nejvýkonnější funkcí spravovaného jazyka C++ je schopnost kombinovat spravovaný a nespravovaný kód na úrovni výrazu a spárovat je s nimi. Žádný jiný jazyk to neumožňuje a při správném použití z něj můžete získat některé silné výhody. Některé příklady si projdeme později.

Spravovaný svět vám také dává obrovský design vyhrává, v tom, že mnoho běžných problémů je postaráno za vás. Pokud chcete, můžete správu paměti, plánování vláken a použití typů nechat na dobu běhu, což vám umožní soustředit svou energii na části programu, které ji potřebují. S MC++ můžete přesně zvolit, kolik ovládacích prvků chcete zachovat.

Programátoři MC++ mají ten luxus v tom, že při kompilaci do IL můžou používat back-end Microsoft Visual C® 7 (VC7) a navíc jiT. Programátoři, kteří jsou zvyklí pracovat s kompilátorem Microsoft C++, jsou zvyklí na věci, které jsou bleskurychlé. JIT byl navržen s různými cíli a má různou sadu silných a slabých stránek. Kompilátor VC7, který není vázán časovými omezeními JIT, může provádět určité optimalizace, které JIT nemůže, jako je analýza celého programu, agresivnější vkládání a registrace. Existují také některé optimalizace, které je možné provádět pouze v prostředích s zabezpečením typu, takže je větší prostor pro rychlost, než umožňuje jazyk C++.

Vzhledem k různým prioritám v JIT jsou některé operace rychlejší než dříve, zatímco jiné pomalejší. Existují kompromisy, které děláte z hlediska bezpečnosti a jazykové flexibility, a některé z nich nejsou levné. Naštěstí existují věci, které programátor může udělat, aby minimalizoval náklady.

Portování: Veškerý kód C++ se dá zkompilovat do jazyka MSIL.

Než budeme pokračovat, je důležité si uvědomit, že do jazyka MSIL můžete zkompilovat libovolný kód jazyka C++. Všechno bude fungovat, ale neexistuje žádná záruka bezpečnosti typu a vy platíte pokutu za seřaďování, pokud budete dělat hodně interop. Proč je užitečné zkompilovat do knihovny MSIL, pokud nemáte žádnou z výhod? V situacích, kdy přesouváte velký základ kódu, vám to umožní postupně převést kód po částech. Pokud používáte MC++, můžete trávit čas přenesením více kódu, místo psaní speciálních obálky, které připevní portovaný a dosud nepředálaný kód dohromady, což může vést k velké výhře. Díky tomu je přenos aplikací velmi čistý proces. Další informace o kompilaci jazyka C++ do jazyka MSIL najdete v možnosti kompilátoru /clr.

Při kompilaci kódu C++ do jazyka MSIL ale nebudete moct získat zabezpečení ani flexibilitu spravovaného světa. Musíte psát v MC++ a v1, což znamená vzdát se několika funkcí. Následující seznam není v aktuální verzi CLR podporován, ale může být v budoucnu. Microsoft se rozhodl podporovat nejběžnější funkce jako první a musel některé další vyříznout, aby bylo možné je dodat. Neexistuje nic, co by bránilo jejich pozdějšímu přidání, ale mezitím je budete muset provést bez nich:

  • Vícenásobná dědičnost
  • Šablony
  • Deterministické dokončování

Pokud tyto funkce potřebujete, můžete vždy spolupracovat s nebezpečným kódem, ale zaplatíte pokutu za výkon při seřazování dat tam a zpět. A mějte na paměti, že tyto funkce lze použít pouze v nespravovaném kódu. Spravovaný prostor nemá žádné znalosti o jejich existenci. Pokud se rozhodnete kód přenést, zamyslete se nad tím, jak moc na tyto funkce ve svém návrhu spoléháte. V několika případech je návrh příliš nákladný a budete chtít zůstat u nespravovaného kódu. Toto je první rozhodnutí, které byste měli udělat, než začnete hackovat.

Výhody MC++ oproti C# nebo Visual Basicu

Mc++ pochází z nespravovaného pozadí a zachovává si spoustu možností zpracování nebezpečného kódu. Schopnost MC++ plynule kombinovat spravovaný a nespravovaný kód poskytuje vývojářům spoustu výkonu a při psaní kódu si můžete zvolit, kde na přechodu chcete sedět. V jednom extrému můžete všechno psát v rovném, nefalšovaném jazyce C++ a jednoduše zkompilovat pomocí /clr. Na druhé straně můžete všechno napsat jako spravované objekty a řešit výše uvedené jazykové omezení a problémy s výkonem.

Ale skutečná síla MC++ přichází, když si vyberete někde mezi. MC++ umožňuje upravit některé výkonové přístupy, které jsou součástí spravovaného kódu, tím, že poskytuje přesnou kontrolu nad tím, kdy používat nebezpečné funkce. C# má některé z těchto funkcí v nebezpečném klíčovém slově, ale není nedílnou součástí jazyka a je mnohem méně užitečný než MC++. Pojďme si projít několik příkladů, které ukazují jemnější členitost dostupnou v MC++, a promluvíme si o situacích, kdy je to užitečné.

Generalizované ukazatele byref

V jazyce C# můžete pouze převzít adresu některého člena třídy tak, že ji předáte parametru ref . V MC++ je ukazatel byref konstruktorem první třídy. Můžete vzít adresu položky uprostřed pole a vrátit ji z funkce:

Byte* AddrInArray( Byte b[] ) {
   return &b[5];
}

Tuto funkci používáme k vrácení ukazatele na "znaky" v souboru System.String prostřednictvím naší pomocné rutiny a pomocí těchto ukazatelů můžeme dokonce procházet pole:

System::Char* PtrToStringChars(System::String*);   
for( Char*pC = PtrToStringChars(S"boo");
  pC != NULL;
  pC++ )
{
      ... *pC ...
}

Můžete také provést procházení propojeného seznamu pomocí injektáže v MC++ tak, že vezmete adresu pole "další" (což není možné udělat v jazyce C#):

Node **w = &Head;
while(true) {
  if( *w == 0 || val < (*w)->val ) {
    Node *t = new Node(val,*w);
    *w = t;
    break;
  }
  w = &(*w)->next;
}

V jazyce C# nemůžete ukazovat na "Hlava" nebo převzít adresu pole "další", takže jste vytvořili zvláštní případ, kdy vkládáte na první místo nebo pokud má hodnota "Hlava" hodnotu null. Kromě toho se musíte v kódu neustále dívat o jeden uzel dopředu. Porovnejte to s tím, co by v dobrém jazyce C# vytvořilo:

if( Head==null || val < Head.val ) {
  Node t = new Node(val,Head);
  Head = t;
}else{
  // we know at least one node exists,
  // so we can look 1 node ahead
  Node w=Head;
while(true) {
  if( w.next == null || val < w.next.val ){
    Node t = new Node(val,w.next.next);
    w.next = t;
    break;
  }
  w = w.next;
  }
}         

Uživatelský přístup k typům s rámečky

Běžným problémem s výkonem jazyků OO je čas strávený balením a rozbalováním hodnot. MC++ poskytuje mnohem větší kontrolu nad tímto chováním, takže pro přístup k hodnotám nebudete muset dynamicky (nebo staticky) odblokovat políčko. Toto je další vylepšení výkonu. Stačí umístit __box klíčové slovo před libovolný typ, který bude představovat jeho krabicový tvar:

__value struct V {
  int i;
};
int main() {
  V v = {10};
  __box V *pbV = __box(v);
  pbV->i += 10;           // update without casting
}

V jazyce C# musíte rozbalit políčko na "v", pak aktualizovat hodnotu a znovu otevřít zpět na objekt:

struct B { public int i; }
static void Main() {
  B b = new B();
  b.i = 5;
  object o = b;         // implicit box
  B b2 = (B)o;            // explicit unbox
  b2.i++;               // update
  o = b2;               // implicit re-box
}

Kolekce STL vs. spravované kolekce – v1

Špatná zpráva: V jazyce C++ bylo používání kolekcí STL často stejně rychlé jako psaní této funkce ručně. Rozhraní CLR jsou velmi rychlá, ale trpí problémy s krabicováním a rozbalováním: vše je objekt a bez šablony nebo obecné podpory je nutné všechny akce zkontrolovat za běhu.

Dobrá zpráva: Z dlouhodobého hlediska se můžete vsadit, že tento problém zmizí s tím, jak se do běhu přidají obecné typy. Kód, který nasadíte dnes, zaznamená zvýšení rychlosti bez jakýchkoli změn. V krátkodobém horizontu můžete ke kontrole použít statické přetypování, ale to už není bezpečné. Tuto metodu doporučuji použít v úzkém kódu, kde je výkon naprosto kritický a vy jste identifikovali dvě nebo tři aktivní místa.

Použití objektů spravovaných službou Stack

V jazyce C++ určíte, že objekt by měl být spravován zásobníkem nebo haldou. I tak to můžete udělat v MC++, ale měli byste mít na paměti nějaká omezení. ClR používá ValueTypes pro všechny objekty spravované zásobníkem a existují omezení pro to, co ValueTypes mohou dělat (například bez dědičnosti). Další informace jsou k dispozici v knihovně MSDN.

Rohový případ: Pozor na nepřímá volání v rámci spravovaného kódu – v1

Za běhu v1 se všechna volání nepřímých funkcí provádějí nativně, a proto vyžadují přechod do nespravovaného prostoru. Jakékoli nepřímé volání funkce lze provést pouze z nativního režimu, což znamená, že všechna nepřímá volání ze spravovaného kódu vyžadují spravovaný přechod na nespravovaný přechod. Jedná se o závažný problém, když tabulka vrací spravovanou funkci, protože je potřeba provést druhý přechod, aby se funkce spustila. V porovnání s náklady na provedení jedné instrukce volání jsou náklady padesátkrát až stokrát pomalejší než v jazyce C++!

Naštěstí při volání metody, která se nachází v třídě uvolňování paměti, optimalizace tuto možnost odebere. V konkrétním případě běžného souboru C++, který byl zkompilován pomocí /clr, bude metoda return považována za spravovanou. Vzhledem k tomu, že tuto možnost nelze odebrat optimalizací, dojde k dosažení úplných nákladů na dvojitý přechod. Níže je příklad takového případu.

//////////////////////// a.h:    //////////////////////////
class X {
public:
   void mf1();
   void mf2();
};

typedef void (X::*pMFunc_t)();


////////////// a.cpp: compiled with /clr  /////////////////
#include "a.h"

int main(){
   pMFunc_t pmf1 = &X::mf1;
   pMFunc_t pmf2 = &X::mf2;

   X *pX = new X();
   (pX->*pmf1)();
   (pX->*pmf2)();

   return 0;
}


////////////// b.cpp: compiled without /clr /////////////////
#include "a.h"

void X::mf1(){}


////////////// c.cpp: compiled with /clr ////////////////////
#include "a.h"
void X::mf2(){}

Existuje několik způsobů, jak se tomu vyhnout:

  • Nastavte třídu na spravovanou třídu ("__gc").
  • Pokud je to možné, odeberte nepřímé volání.
  • Ponechte třídu zkompilovanou jako nespravovaný kód (např. nepoužívejte /clr).

Minimalizace přístupů z hlediska výkonu – verze 1

Existuje několik operací nebo funkcí, které jsou v MC++ ve verzi 1 JIT jednoduše dražší. Vypíšem je a vysvětlím si je a pak si promluvíme o tom, co s nimi můžete dělat.

  • Abstrakce – jedná se o oblast, kde back-endový kompilátor C++ výrazně vyhrává nad JIT. Pokud zabalíte int do třídy pro abstrakční účely a budete k němu přistupovat výhradně jako int, může kompilátor jazyka C++ snížit režii obálky prakticky na nic. Do obálky můžete přidat mnoho úrovní abstrakce, aniž byste zvýšili náklady. JIT není schopen využít čas potřebný k odstranění těchto nákladů, což v MC++ prodražuje hloubkové abstrakce.
  • Plovoucí desetiná čárka – JIT v1 momentálně neprovádí všechny optimalizace specifické pro FP jako back-end VC++, takže operace s plovoucí desetinou čárkou jsou teď dražší.
  • Multidimenzionální pole – JIT je při zpracování zubatých polí lepší než multidimenzionální pole, takže místo nich používejte zubatá pole.
  • 64bitová aritmetická – V budoucích verzích budou do JIT přidány 64bitové optimalizace.

Co můžete dělat

V každé fázi vývoje můžete dělat několik věcí. S MC++ je fáze návrhu pravděpodobně nejdůležitější oblastí, protože určí, kolik práce nakonec uděláte a kolik výkonu získáte na oplátku. Když se posadíte k zápisu nebo přenosu aplikace, měli byste zvážit následující věci:

  • Identifikujte oblasti, ve kterých se používá více dědičnost, šablony nebo deterministické finalizace. Budete se jich muset zbavit, jinak tuto část kódu necháte v nespravovaném prostoru. Zamyslete se nad náklady na přepracování a určete oblasti, které je možné přenést.
  • Vyhledejte aktivní body výkonu, jako jsou hluboké abstrakce nebo volání virtuálních funkcí ve spravovaném prostoru. Ty budou také vyžadovat rozhodnutí o návrhu.
  • Vyhledejte objekty, které byly zadány jako spravované zásobníkem. Ujistěte se, že se dají převést na ValueTypes. Označte ostatní pro převod na objekty spravované haldou.

Během fáze kódování byste měli znát operace, které jsou dražší, a možnosti, které máte při jejich řešení. Jednou z nejhezčích věcí na MC++ je, že se před zahájením psaní kódu vypořádáte se všemi problémy s výkonem: to je užitečné při pozdějším odbourávání práce. Stále však existují některé vylepšení, které můžete při kódování a ladění provádět.

Určete, které oblasti využívají aritmetické funkce s plovoucí desetinou čárkou, multidimenzionální pole nebo funkce knihovny. Která z těchto oblastí je kritická pro výkon? Pomocí profilátorů vyberte fragmenty, u kterých vás režie stojí nejvíce, a vyberte, která možnost se zdá být nejlepší:

  • Uchovávejte celý fragment v nespravovaném prostoru.
  • Při přístupu ke knihovně používejte statická přetypování.
  • Zkuste upravit chování boxingu/unboxingu (vysvětlení najdete později).
  • Naprogramujte si vlastní strukturu.

Nakonec pracujte na minimalizaci počtu přechodů, které provedete. Pokud máte nějaký nespravovaný kód nebo volání zprostředkovatele komunikace ve smyčce, nastavte celou smyčku jako nespravovanou. Tímto způsobem zaplatíte náklady na přechod pouze dvakrát, a ne za každou iteraci smyčky.

Další materiály

Mezi související témata týkající se výkonu v rozhraní .NET Framework patří:

Podívejte se na budoucí články, které jsou aktuálně ve vývoji, včetně přehledu návrhu, architektury a kódování, návodu k nástrojům pro analýzu výkonu ve spravovaném světě a porovnání výkonu platformy .NET s dalšími podnikovými aplikacemi, které jsou dnes k dispozici.

Příloha: Náklady na virtuální volání a přidělení

Typ volání Počet volání za sekundu
Ne virtuální volání ValueType 809971805.600
Ne virtuální volání třídy 268478412.546
Virtuální volání třídy 109117738.369
Volání ValueType Virtual (Obj – metoda) 3004286.205
Volání ValueType Virtual (Overridden Obj – metoda) 2917140.844
Typ načtení podle nového (nestatické) 1434.720
Typ načtení podle nového (virtuální metody) 1369.863

Poznámka: Testovací počítač je PIII 733 MHz se systémem Windows 2000 Professional s aktualizací Service Pack 2.

Tento graf porovnává náklady spojené s různými typy volání metod a také náklady na vytvoření instance typu, který obsahuje virtuální metody. Čím vyšší je číslo, tím více volání a instancí za sekundu lze provést. I když se tato čísla budou na různých počítačích a konfiguracích určitě lišit, relativní náklady na provedení jednoho volání nad druhým zůstávají značné.

  • ValueType Non-Virtual Call: Tento test volá prázdnou ne virtuální metodu obsaženou v ValueType.
  • Ne virtualické volání třídy: Tento test volá prázdnou ne virtuální metodu obsaženou ve třídě.
  • Virtuální volání třídy: Tento test volá prázdnou virtuální metodu obsaženou ve třídě.
  • Volání ValueType Virtual (Obj method): Tento test volá ToString() (virtuální metoda) u ValueType, který se uchýlí k výchozí objektové metodě.
  • Volání ValueType Virtual (metoda Overridden Obj): Tento test volá ToString() (virtuální metoda) u ValueType, který přepsal výchozí hodnotu.
  • Typ zatížení podle nového (statického): Tento test přidělí místo pro třídu pouze se statickými metodami.
  • Typ zatížení podle nového (virtuální metody): Tento test přidělí místo pro třídu s virtuálními metodami.

Jedním z závěrů, které můžete vyvodit, je, že volání virtuální funkce jsou při volání metody ve třídě přibližně dvakrát dražší než běžná volání. Mějte na paměti, že volání jsou na začátku levné, takže bych neodebrala všechna virtuální volání. Virtuální metody byste měli vždy používat, pokud to dává smysl.

  • JIT nemůže vložit virtuální metody, takže pokud se zbavíte ne virtuálních metod, přijdete o potenciální optimalizaci.
  • Přidělení prostoru pro objekt, který má virtuální metody, je o něco pomalejší než přidělení pro objekt bez nich, protože je třeba udělat další práci, aby se místo pro virtuální tabulky našli.

Všimněte si, že volání ne-virtuální metody v rámci ValueType je více než třikrát rychlejší než ve třídě, ale jakmile s ní zacházíte jako s třídou , strašně ztratíte. To je charakteristické pro ValueType: zachází s nimi jako se strukturami a rychle svítí. Chovejte se k nim jako ke třídám a jsou bolestně pomalé. ToString() je virtuální metoda, takže před jejím zavoláním musí být struktura převedena na objekt na haldě. Místo toho, aby bylo dvakrát pomalé, volání virtuální metody pro ValueType je nyní osmnáctkrát pomalé! Morální příběh? Nechovejte ValueTypes jako třídy.

Pokud máte dotazy nebo připomínky k tomuto článku, obraťte se na Claudio Caldato, programový manažer pro problémy s výkonem rozhraní .NET Framework.