Tipy pro zvýšení výkonnosti MVC aplikací

Miroslav Holec       4. 10. 2013       C#, ASP.NET MVC, Optimalizace       5525 zobrazení

Přestože nejsem zastáncem předčasných optimalizací vůči neznámé budoucnosti aplikace, výchozí výkonnost MVC aplikací není zdaleka optimální. Defaultně vytvořená MVC aplikace se snaží uspokojit širokou škálu vývojářů bez ohledu na jejich skutečný záměr. Stačí přitom vynaložit relativně málo úsilí k tomu, aby se výchozí výkonnost MVC aplikací znásobila. Výkonnost příkladů v tomto článku byla hodnocena měřením počtu requestů, které byla aplikace schopna před a po úpravě obsloužit.

Zobrazovací engine

MVC aplikace napsané na platformě .NET v současné době umožňují využití různých zobrazovacích engines. Většina vývojářů podvědomě zná WebFormViewEngine z prvních verzí MVC, případně oblíbenější Razor, který je aktuálně ve verzi 3.0.

Ve výchozím stavu nabízí MVC aplikace možnost vybrat si z obou zmíněných engines, které jsou již zaregistrovány.

image

Samozřejmě každý registrovaný ViewEngine má vliv na výkonnost celé aplikace a to i přesto, že fakticky při vývoji používáte například jen Razor. Přehled engines použitelných pro ASP.NET MVC je důkladně popsán na v článku na StackOverflow. Není-li tedy nezbytně nutné, je vhodné použít jen jeden engine a ten také v Global.asax zaregistrovat.

protected void Application_Start() 
{ 
    ViewEngines.Engines.Clear(); 
    ViewEngines.Engines.Add(new RazorViewEngine()); 
}

Závadný kód se zbytečnými engines 2276 REQ/S
Kód registrující pouze Razor 3288 REQ/S

NULL hodnoty v Action metodách

Většina ukázkových aplikací postavených na MVC obsahuje příklady, ve kterých se volá na konci Action metod metoda View() bez předaných parametrů (modelu). Často pak následně ve View vytváříme nový objekt (například Article), který samozřejmě pracuje s hodnotou NULL. Příklad závadného kódu může vypadat následovně:

ppublic class HomeController : Controller
{
    public ActionResult Index()
    {
    	return View();
    }
}
 
public class Article
{
    public string Name { get; set; }
}

@model Performance.Controllers.Article
@{
    ViewBag.Title = "Index";
}
 
@Html.TextBoxFor(m=> m.Name)

Pokud bychom tento kód debugovali až na úroveň tříd .NET frameworku, zjistíme, že způsobuje NullReferenceException, která je sice zachycena, ale její zpracování stojí čas a prostředky. Je-li View komplikovanější, výjimek vznikne celá řada a výkonnost dané stránky se rapidně sníží. Přitom stačí předat instanci objektu a výkonnost se opět zvýší:

public ActionResult Index()
{
return View(new Article());
}

Závadný kód 1651 REQ/S
Správný kód 1900 REQ/S

Volba Debug/Release módu

Aplikaci bychom měli před publikací na produkční prostředí přepnout do Release módu. Mnoho začínajících vývojářů publikuje v Debug módu s tím, že většinou nevidí rozdíl. Kromě využití Debug/Release módu pro extrahování různých konfigurací pomocí transformací konfiguračního souboru Web.Config používá nastavení i .NET Framework, konkrétně například třída VirtualPathProviderViewEngine.cs při nastavování cachování (v releasu módu 15 minut).

  public class DefaultViewLocationCache : IViewLocationCache
  {
    private static readonly TimeSpan _defaultTimeSpan = new TimeSpan(0, 15, 0);
    public static readonly IViewLocationCache Null = 
(IViewLocationCache) new NullViewLocationCache();

Snippet z assembly Systém.Web.MVC, který pracuje s módem:

protected VirtualPathProviderViewEngine()
{
    if (HttpContext.Current == null || HttpContext.Current.IsDebuggingEnabled)
        this.ViewLocationCache = DefaultViewLocationCache.Null;
    else
        this.ViewLocationCache = (IViewLocationCache) new DefaultViewLocationCache();
}

Závadný kód 1852 REQ/S
Správný kód 1896 REQ/S

Předání cest k View v rámci volání Html.Partial()

Bez většího zkoumání tříd a metod ASP.NET MVC frameworku používají vývojáři při volání Html.Partial() odkaz na view v té podobě, jak jej vidí v solution exploreru.

@{
    ViewBag.Title = "Index";
}
 
Statický obsah
@Html.Partial("_MyPartial")

Elegance tohoto funkčního kódu je však opět vykoupena výkonností stránky. Interní metoda FindPartialView třídy HtmlHelper totiž nejprve zkouší, zda je uvedena celá cesta k View. Pokud není uvedena cesta ale pouze název View, jako v tomto případě, spouští se prohledávání různých lokací, viz. snippet z třídy HtmlHelper:

StringBuilder stringBuilder = new StringBuilder();
    foreach (string str in partialView.SearchedLocations)
    {
        stringBuilder.AppendLine();
        stringBuilder.Append(str);
    }
    throw new InvalidOperationException(string.Format((IFormatProvider) CultureInfo.CurrentCulture, MvcResources.Common_PartialViewNotFound, new object[2]
    {
         (object) partialViewName,
         (object) stringBuilder
    }));

Pokud se View nenajde, uvidíme Exception, která mimo jiné vypisuje seznam všech míst, kde došlo k hledání.

image

Počet prohledávaných míst samozřejmě narůstá, pokud máme stále registrovaný starší WebFormViewEngine:

 

image

Řešením problému je předávat celou cestu k View:

@Html.Partial(@"~/Views/Shared/_MyPartial.cshtml")
Volání Html.Partial("_View") - Razor + WebFormViewEngine 1387 REQ/S
Volání Html.Partial("_View") - pouze Razor aktivní 2276 REQ/S
Volání Html.Partial(@"Path/_View.cshtml") 2887 REQ/S

Output Cache

O OutputCache by se dal napsat samostatný rozsáhlý článek. Obecně OutputCache umožňuje cachování obsahu vráceného Action metodou Controlleru, čímž se dá významně zvýšit výkonnost aplikace. Spíše než problém použití je otázka, kdy a jakým způsobem OutputCache nastavit. Speciálně v případě personalizovaných stránek (komunitní servery, eshopy) je nasazení komplikovanější. U běžných stránek, které se často nemění je ale její použití snadné a užitečné.

Statická stránka s HTML obsahem 3266 REQ/S
Statická stránka s HTML obsahem a OutputCache (15 s) 8682 REQ/S

Naměřené hodnoty jsou lákavé. Problém nasazení u stránek s personalizovaných obsahem (například zmíněné eshopy) se dá řešit více způsoby. Dvě řešení, které pokrývají běžné potřeby jsou ASP.NET DonutCaching nebo použití JavaScriptu.

Extension: ASP.NET Donut Caching

ASP.NET Donut Caching (http://mvcdonutcaching.codeplex.com/) je užitečný modul, který umí cachovat obsah stejně jako standardní OutputCache ale kromě toho lze přímo ve Views nastavit odlišnou cachovací politiku při volání @Html.Action() a tím určité části stránky z cachování vyloučit. Donut Caching lze nainstalovat přes NuGet a používá se prakticky stejně jako OutputCache.

AJAX

Pracnější ale v mnoha případech efektivní řešení je cachovat HTML obsah, který je společný, zatímco personalizovaný obsah načítat AJAXem po načtení DOMu. V případě zmíněného e-shopu je toto řešení často ideální, protože informace o produktech se příliš často nemění, zatímco obsah košíku ano.

Závěr

Způsobů, jak zvýšit výkonnost MVC aplikace je celá řada. V článku jsem se záměrně věnoval příkladům, které jsou snadné na implementaci a takřka okamžitě umožní aplikaci obsloužit mnohem více requestů za čas. Moje osobní doporučení je věnovat občas pozornost třídám a metodám, které denně používáme a čas od času si prohlédnout jejich vnitřní implementaci.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

MVC 5 optimalizace - zobrazovací ViewEngine

Jen přípomínka k odebírání View Engines:

Ve verzi MVC5 je tohle vyřešené a už je rozdíl minimální (já jsem nenaměřil žádný). Se starší verzí jsem naměřil +- stejný výsledek jako autor článku.

Problém byl způsobem prohledáváním složek na serveru při vyhledávání šablon (šablona může být umístěna na několika místech a navíc jako aspx a/nebo ascx). Cache tam byla už dřív, ale až v aktuální verzi je vyřešena tak, aby nebrzdila téměř vůbec.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Článek a VB.NET :D:D:D

Článku s ukázkou kódu ve VB.NET už se asi nedočkáme.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Zobrazovací engine

Článek je zajímavý, ale chtělo by to se trošku rozepsat o důvodech (např jako je v té části s nullreference). Jakožto neMVC vývojáře mě extrémně zarazil ten první rozdíl: nedokážu si představit, že by výběr enginu měl zabrat třetinu requestu. Jde to nějak rozumně vysvětlit? Tam musí být něco velmi shnilého.

Jakub Čermák

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback