Soci (Soczó Zsolt) szakmai blogja

2009.11.30.

Managed memory leak nyomozás WinDBG-vel

Filed under: .NET,C#,Szakmai élet — Soczó Zsolt @ 10:55

Imádom a WinDbg-t, mondtam már? Az egyik kis programom módszeresen eszegette a memóriát. Ciklusban végez feldolgozást, rettentő sok adattal, és ezek egy része szépen bennragadt a memóriában. .NET-ben nincs memory leak a klasszikus értelemben, de van oly módon, hogy a rootokból marad referencia egyes objektumokra, így azok élve maradnak, szándékaink ellenére. Néha azonban nem triviális kinyomozni, melyik root miatt ragadt be valami a memóriába.
A VSTS profiler segítségével odáig el lehet jutni, hogy ki ragad be a memóriába. Az Object LifeTime nézetben az Instances alive at end oszlopra rendeztetve láthatjuk, kik maradnak élve a program végén. Én beraktam egy force-olt GC-zést a program végére, így aki ezek után még benn maradt, azok egy része szándékaim ellenére tette ezt, ezért azt leaknek tekintem, és meg kell szüntetni.
Jöhet a WinDbg. File, Open Executable, F5. A program végén a GC után raktam még egy Console.ReadLine()-t. Amikor ide eljut, a debuggerben CTRL-Breakkel megállítom a program futását (várakozását). Betöltöm az sos-t:
.load C:\Windows\Microsoft.NET\Framework64\v4.0.21006\sos.dll

Kilistáztatom a GC heapen levő ojjektumokat:
!DumpHeap -stat

000007ff005c2278 2766 221280 System.Data.Metadata.Edm.TypeUsage
000007fef1b90d00 370 222136 System.Byte[]
000007fef1b89be0 13379 1330896 System.String
000007fef1b8c8a8 1125 1447552 System.Int32[]
000007fef1b3bef0 9298 3224392 System.Object[]
000007ff00274988 245954 29514480 ATS.Bar

Akinek nem szabadna már a memóriában lenni, az az ATS.Bar objektumok, 245954 darab, 29514480 bájt méretben. Nézzük megy a példányokat belőle:

!DumpHeap -type ATS.Bar

0000000004952c60 000007ff00274988 120
0000000004952cd8 000007ff00274988 120
0000000004952d50 000007ff00274988 120
0000000004952dc8 000007ff00274988 120
0000000004952e40 000007ff00274988 120
0000000004952eb8 000007ff00274988 120
total 0 objects
Statistics:
MT Count TotalSize Class Name
000007ff00fb38a0 1 24 System.Collections.Generic.GenericEqualityComparer`1[[ATS.BarCollectionDescriptor, Common]]
000007ff00271050 1 24 ATS.BarDAL
000007ff00270e68 1 48 ATS.BarFactory
000007ff00fb31b8 1 88 System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]]
000007ff00fb27a8 1 88 System.Collections.Generic.Dictionary`2[[ATS.BarCollectionDescriptor, Common],[ATS.BarFromTickFactory, Common]]
000007ff00275378 1 88 System.Collections.Generic.Dictionary`2[[ATS.Symbol, Common],[ATS.BarCollectionByInterval, Common]]
000007ff00fb3ee0 1 96 System.Collections.Generic.Dictionary`2+Entry[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]][]
000007ff00fb3b10 1 96 System.Collections.Generic.Dictionary`2+Entry[[ATS.BarCollectionDescriptor, Common],[ATS.BarFromTickFactory, Common]][]
000007ff00fb0498 1 96 System.Collections.Generic.Dictionary`2+Entry[[ATS.Symbol, Common],[ATS.BarCollectionByInterval, Common]][]
000007ff00fb2d30 3 120 System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]]
000007ff00790990 3 120 ATS.BarCollectionDescriptor
000007ff00fb1568 7 168 System.Collections.ObjectModel.ObservableCollection`1+SimpleMonitor[[ATS.Bar, Common]]
000007ff00886530 3 240 ATS.BarFromTickFactory
000007ff00fb1648 7 280 System.Collections.Generic.List`1[[ATS.Bar, Common]]
000007ff00273788 7 952 ATS.BarCollection
000007ff00274988 245954 29514480 ATS.Bar
Total 245993 objects

A kis 120 bájtos izék a problémásak (a végén az összefoglaló azért tartalmaz több típust is, mert substring szűrést csinál a -type). Az első oszlop az egyedi objektumok címe, a második a típus metódusleíró táblája.
És most jön a lényeg. Ki miatt érhető el a rootokból mondjuk az utolsó Bar példány?

!GCRoot 0000000004952eb8

DOMAIN(00000000003CF530):HANDLE(Pinned):1217d8:Root: 0000000012657048(System.Object[])->
0000000002bf7f28(System.Collections.Generic.Dictionary`2[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]])->
0000000002bf94b8(System.Collections.Generic.Dictionary`2+Entry[[System.Int32, mscorlib],[System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]], mscorlib]][])->
0000000002f06ca0(System.Collections.Generic.List`1[[ATS.BarFromTickFactory, Common]])->
0000000002f06cc8(System.Object[])->
0000000002f05860(ATS.BarFromTickFactory)->
0000000004952eb8(ATS.Bar)

Ebben az látszik, az első bejegyzésből, amit még a CLR startup hozzott létre hogyan lehet eljutni a beragadt objektumunkhoz. A CLR generikus neveket C#-ra visszafordítva az látszik, hogy egy Dictionary> hivatkozik egy Dictionary.Entry>-re, az a dictionary belső tárolóeleme. Ez rámutat egy List-re, ami továbbmutat egy ATS.BarFromTickFactory-ra, ami hivatkozik a Bar-ra.

Ez alapján már rekonstruálható a probléma forrása. A probléma elemi oka, hogy túlzásba vittem a statikusok használatát, és nem figyeltem a takarításukra.

static readonly Dictionary FactoriesByDesc =
new Dictionary();

Ebből rendesen kiszedtem a tárolt BarFromTickFactory példányt, ha már nem volt rá szükség. De elfeledkeztem róla, hogy volt egy másik kollekció is:

public static readonly Dictionary> FactoryListBySymbolId =
new Dictionary>();

Ebből viszont nem szedtem ki a hivatkozás az adott BarFromTickFactory-ra, így az szépen beragadt a memóriába.

Tanulságok:
1. Szeretjük a WinDbt-t.
2. Kerüljük a statikusokat. Ha a BarFromTickFactory példányokat egy mások osztály példányai tárolnák, akkor ha azok kifutnak a szkópból, automatikusan a GC martalékai lesznek. A sok statikus sok odafigyelést igényel, kár erőltetni őket.

2009.11.28.

A Windows 7 megmentette a laptopomat a leégéstől

Filed under: Hardver,Szakmai élet,Windows,Windows 7 — Soczó Zsolt @ 10:28

Két hete vettem a fáradtságot, és felraktam az eddigi Windows 2008 helyére a Windows 7-et. (Kicsit nehezen szántam rá magam, mert minden OS váltás után vagy egy hét mire minden újra a helyére kerül.)
Hamarosan észrevettem, hogy nagyon belassul a gép. A Resource Monitor CPU fülén a Processes fejlécen látható egy érték, % Maximum Frequency. Ez azt jelzi, mennyire vette vissza a Windows (vagy a BIOS?, nem tudom) a proci frekvenciáját. A belassulások idején ez kb. 20%-on állt, és nem akar feljebb menni. Bosszantó, leszabályozza a Windows a proci sebességemet. Mivel nem elégedtem meg egy egyszerű k. Windows beszólással (másképp mehetnék HUP tagnak), utánanéztem a dolognak. Azt írják, ha a gép hűtésével van gond, akkor történhet ilyen. Volt, pl., akinek leállt a proci ventilátora.
Tesztként kikapcsoltam a BIOS-ban a SpeedStepet, ettől mindig 100%-on volt a sebesség, de egész nap süvített a gép, és lesütötte a t.met is.
Végül bekapcsoltam a kompresszort, és alaposan kifújattam a port a gépből. Azóta hideg a gép, mégis, ha kell, felhúzza a Windows 100%-ra a procisebességet (a BIOS-ban újra engedélyeztem a SpeedStepet).
Terjesszétek a jó hírt, ha belassul a gép, tessék alaposan kiporolni.

Apró objektumok szemetelése

Filed under: .NET,C#,Szakmai élet — Soczó Zsolt @ 09:38

Az előző bejegyzéshez kapcsolódik még az alábbi. Sok felesleges memóriaallokállást és aztán GC-zést okoztam az alábbi kóddal:


OnBarArrived(new BarArrivedEventArgs(bar));

private void OnBarArrived(BarArrivedEventArgs e)
{
if (BarArrived != null)
{
BarArrived(this, e);
}
}

Ha a BarArrived event null, azaz nem iratkozott fel senki az eventre, akkor feleslegesen hozok létre egy BarArrivedEventArgs-ot. A javított verzió így néz ki:

private void OnBarArrived(Bar bar)
{
if (BarArrived != null)
{
BarArrived(this, new BarArrivedEventArgs(bar));
}
}

A bar objektum már úgyis kész van, az eventargot viszont csak akkor hozom létre, hogy tényleg szükség van rá.

2009.11.27.

Naív ojjektumhasználat – sok szemét

Filed under: .NET,C#,CLR,Szakmai élet — Soczó Zsolt @ 23:16

Két hete elkezdtem intenzíven használni a Visual Studio 2010-et, mivel szükségem volt a MemoryMappedFiles-ra a 4.0-s fwből. Ezt shared memoryként használom, amivel a laptopon kb. 1GByte/sec-kel tudok adatokat másolni két processz között, így kiváló cache-t tudtam építeni a segítségével. De most nem erről lesz szó.
A profilert is jelentősen továbbfejlesztették, van benne pl. rendes memória allokálás követés (lehet, hogy a régi is tudta ezt, akkor pardon).
Nézzük az alábbi képet:

A SecondInterval.Day property-t közel 6 milliószor hívtam meg, minden híváskor előállítva egy új ojjektumot, 140MByte-nyi szemetet hagyva magam után.

A naív implementáció így nézett ki:

public static SecondInterval Day
{
get { return new SecondInterval(86400); }
}

Maga a típus egy egyszerű Whole Value pattern implementáció, egy sima immutable objektum, ráadásul class, így referenciális típus.

Nem memóriapazarló módon így néz ki a property:

private static readonly SecondInterval aDay = new SecondInterval(86400);
public static SecondInterval Day
{
get { return aDay ; }
}

A readonly fontos, hogy nehogy valaki kiüsse a referenciámat, és berakjon egy sunyi másik időt reprezentáló ojjektumot a napi helyére. Az ojjektum immutable, ez fontos, másként semmit nem érne a readonly, a gyomrát lehetne piszkálgatni.
Amin még el lehetne gondolkodni, hogy struktúrává, value type-pá átalakítani a típust, csak akkor meg minden helyen ahol használom, másolni kellene az értékét. Mivel ez most belül egy 32 bites int, és 64 bit alatt futtatom, ahol a referenciák 64 bitesek, a másolással még mindig jobban járok. Úgyhogy lehet, hogy struct lesz, de előbb megnézem classként hogy muzsuikál.

2009.11.15.

“Holnapra megölünk minden magyart!”

Filed under: Élet — Soczó Zsolt @ 19:56

A galamblelkűek, a diszkrimináltak. A forum.index.hu lehalt, az index.hu is akadozik.

Powered by WordPress