Profilování paměti

Dynamics AX nabízí spoustu možností, jak volat .NETový kód, používat .NET GUI prvky a podobně. To je úplně skvělé – umožňuje to používat existující .NETové typy a frameworky nebo jednoduše psát kód v jazyce, který se pro dané řešení hodí nejlépe. Ale jako vždy: “S velkou mocí přichází i velká zodpovědnost”.

Jeden z potenciálních problémů s .NET komponentami je v únicích paměti (memory leak). Přestože .NET používá garbage collector k uvolnění paměti alokované již nepoužívanými objekty, pochopitelně to nemůže udělat pokud na objekt stále existuje nějaká reference. Například pokud statické pole obsahuje referenci na nějaký objekt, tento nemůže být odstraněn, dokud neodebereme danou referenci nebo není odstraněna celá aplikační doména. Bohužel, v komplikovanější aplikaci často není tak snadné problém lokalizovat.

Momentálně takový problém řeším s jednou aplikací, kterou jsem zdědil – je to v zásadě WPF formulář integrovaný do AX2009 pomocí ActiveX a obousměrně komunikující s AX. Pokud formulář otevřete a zavřete (a spustíte garbage collection), paměť by měla být uvolněna – celková spotřeba paměti by měla být zhruba stejná před a po spuštění formuláře. Problém je, že ve skutečnosti není uvolněna skoro žádná paměť – neklamné znamení úniku paměti.

K analýze problému jsem použil ANTS Memory Profiler – a udělal svou práci velmi dobře.

Prvním krokem bylo nadefinovat jaká aplikace se má spustit a s jakými parametry – v tomto případě to byl klient Dynamics AX, ale mohli byste zvolit příklad Windows server k profilování .NET kódu běžícího na AOS.

Jakmile stisknete Start Profiling, aplikace se spustí s připojeným profilerem (nebo lze připojit profiler k již běžícímu .NET 4 procesu, ale to se netýká klienta AX2009). Pak zkrátka používáte danou aplikaci ke spuštění částí, které vás zajímají, a dle potřeby vytváříte snaposhoty paměti. Později můžete analyzovat obsah libovolného snapshotu nebo porovnat rozdíly mezi dvěma snapshoty.

Na obrázku níže můžete vidět jeden snapshot před otevřením WPF formuláře (modrá vertikální čára v grafu) a další snapshot po zavření formuláře (červená nepřerušovaná vertikální čára). Rozdíl v spotřebě paměti je 55 megabytů – to není zrovna málo.

Máte celkem dost možností, jak data analyzovat; nejzákladnější je na úrovni tříd. Na záložce Class list můžete vidět, jaké třídy mají nejvíc instancí, konzumují nejvíce paměti, a jak se čísla změnila mezi snapshoty. Toto je příklad tří tříd s největší spotřebou paměti v testované aplikaci:

Třída DM_OpenVisit je zde velmi podezřelá – drží spoustu dat (většinou jako text) a její instance by vážně neměly existovat po zavření formuláře. Ta složitá otázka je, co odkazuje na tyto instance, protože ke skutečnému viníkovi může vést dlouhá sekvence odkazů a některé reference nemusí být právě očividné.

Co nám pomůže je podívat se na graf závislostí (Instance Retention Graph) pro nějakou instanci dané třídy:

Graf ukazuje strom odkazů, které brání, aby byla instance odstraněna. Pokud bychom sledovali například levou větev, dostali bychom se k této referenci:

Jak je zřejmé, něco sleduje změny nějaké dependency property. O jakou vlastnost jde můžeme zjistit rozbalením detailů o daném Dictionary:

Najít kód používající vlastnost ActualHeight je už snadné.

Že použití třídy DependencyPropertyDescriptor může způsobit únik paměti není nic nového. Opravit tento problém by už mělo být snadné – ta obtížná část je nalezení zdroje úniku paměti a se správným nástrojem to zjevně není tak složité.

Graf závislostí ukazuje ještě jednu zajímavou věc – světle modrý obdélník seskupuje objekty, které všechny (nepřímo) odkazují na všechny ostatní. Tudíž jakákoli reference na jakýkoli objekt v obdélníku zabraňuje všem, aby byly odebrány garbage collectorem. Implementace používá jeden kořenový uzel, který drží všechna data, a mnoho objektů odkazuje na tento kořen, takže efektivně také drží všechna data. S jiným návrhem by stejná chyba mohla vést k znatelně menšímu úniku paměti.

Hlavní ponaučení pro vývojáře Dynamics AX by mělo být, že používání .NETových komponent má nejen výhody, ale také některé potenciální háčky. Není překvapivé, že pokud chceme psát a používat řešení založená na .NET, musíme rozumět souvisejícím problémům a používat příslušné nástroje, abychom se s nimi mohli vypořádat.