Walkthrough: Profiling With Automated Tests
Sunday, May 9th, 2010Egyszerű, ha tudod hol kell keresni.
Egyszerű, ha tudod hol kell keresni.
Futtatni akartam pár régebbi unit tesztemet, de nem mentek, azt mondta, nem találja a Microsoft.ACE.OLEDB drivert. Tévesen több helyen azt láttam, hogy azt kell beírni, hogy Microsoft.ACE.OLEDB.14.0, de nem, a registryben is a régi, 12-es verzió van. Megnéztem, az InprocServer32 a C:\Program Files\Common Files\Microsoft Shared\OFFICE14\ACEOLEDB.DLL-re mutat, ami a manifestje alapján 64 bites C runtime dlleket használ, szóval tényleg van 64 bites access driver. (Az InprocServer32 név jó nagy fiaskó, minden ilyen bedrótozott verzió visszaüt később, mint a 16 bites shortParam, ami valójában 32 bites int).
Sejtettem, hogy 64 bit - 32 bit probléma van, de nem jöttem rá mi az oka, míg a tesztben ki nem írattam a teszt futtató bitszámát: Environment.Is64BitProcess == false.
Ekkor csaptam a homlokomra, hogy alapban 64 bites gépen is 32 bites processzben futnak a tesztek. De szerencsére át lehet állítani.
Ti ne töltsetek el 1 órát ilyen marhasággal, emlékezzetek erre. :)
Játszottam kicsit az EF4-gyel. Az alábbi kód egy n rétegű app adatmozgását szimulálja a WCF xml szerializálóját használva. Mindhárom template-tel kipróbáltam, alább láthatóak az adatmozgások.
A tesztkód messze nem korrekt, de kiindulópontként további vizsgálatokhoz elfogatható:
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
namespace POCO1
{
class Program
{
static void Main()
{
Department d;
using (var e = new SchoolEntities())
{
e.ContextOptions.ProxyCreationEnabled = false;
e.ContextOptions.LazyLoadingEnabled = false;
d = e.Departments.Include("Courses").Single(dep => dep.DepartmentID == 1);
Console.WriteLine("{0}", d.Name);
Console.WriteLine("---------------------");
foreach (Course c in d.Courses)
{
Console.WriteLine("{0}", c.Title);
}
e.Detach(d);
}
var ser = new DataContractSerializer(d.GetType());
//var ser = new DataContractSerializer(d.GetType(),
//null, 50000, true, true, null, new ProxyDataContractResolver());
using (var s2c = new FileStream(@"c:\temp\Server2Client.xml", FileMode.Create, FileAccess.ReadWrite))
{
//1. Server küld kliensre
ser.WriteObject(s2c, d);
s2c.Position = 0;
//2. Kliens deserializál
var clientSideDep = (Department)ser.ReadObject(s2c);
//Csak ST
//bool ce = clientSideDep.ChangeTracker.ChangeTrackingEnabled;
//3. Kliens módosít
clientSideDep.Name += "a";
using (var c2s = new FileStream(@"c:\temp\Client2Server.xml", FileMode.Create, FileAccess.ReadWrite))
{
//4. Kliens visszaküld
ser.WriteObject(c2s, clientSideDep);
c2s.Position = 0;
//5.Server deserializál
var sentBackDepartment = (Department)ser.ReadObject(c2s);
using (var e = new SchoolEntities())
{
//6. Server visszamódosít
//Normál entitás
e.Departments.Attach(sentBackDepartment);
e.ObjectStateManager.GetObjectStateEntry(sentBackDepartment).SetModified();
e.ObjectStateManager.GetObjectStateEntry(sentBackDepartment).SetModifiedProperty("Name");
//POCO
//e.Departments.Include("Courses").Single(dep => dep.DepartmentID == 1);
//e.Departments.ApplyCurrentValues(sentBackDepartment);
//Self-tracking entity
//e.Departments.ApplyChanges(sentBackDepartment);
e.SaveChanges();
}
}
}
}
}
}
EF alapobjektumok szerviz => kliens:
<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<EntityKey z:Id="i2" xmlns="http://schemas.datacontract.org/2004/07/System.Data.Objects.DataClasses" xmlns:a="http://schemas.datacontract.org/2004/07/System.Data">
<a:EntityContainerName>SchoolEntities</a:EntityContainerName>
<a:EntityKeyValues>
<a:EntityKeyMember>
<a:Key>DepartmentID</a:Key>
<a:Value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">1</a:Value>
</a:EntityKeyMember>
</a:EntityKeyValues>
<a:EntitySetName>Departments</a:EntitySetName>
</EntityKey>
<Administrator>2</Administrator>
<Budget>350000.0000</Budget>
<Courses/>
<DepartmentID>1</DepartmentID>
<Name>Engineering</Name>
<StartDate>2007-09-01T00:00:00</StartDate>
</Department>
EF alapobjektumok kliens => szerviz:
<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<EntityKey z:Id="i2" xmlns="http://schemas.datacontract.org/2004/07/System.Data.Objects.DataClasses" xmlns:a="http://schemas.datacontract.org/2004/07/System.Data">
<a:EntityContainerName>SchoolEntities</a:EntityContainerName>
<a:EntityKeyValues>
<a:EntityKeyMember>
<a:Key>DepartmentID</a:Key>
<a:Value i:type="b:int" xmlns:b="http://www.w3.org/2001/XMLSchema">1</a:Value>
</a:EntityKeyMember>
</a:EntityKeyValues>
<a:EntitySetName>Departments</a:EntitySetName>
</EntityKey>
<Administrator>2</Administrator>
<Budget>350000.0000</Budget>
<Courses/>
<DepartmentID>1</DepartmentID>
<Name>Engineeringa</Name>
<StartDate>2007-09-01T00:00:00</StartDate>
</Department>
POCO szerviz => kliens:
<Department xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Administrator>2</Administrator> <Budget>350000.0000</Budget> <Courses/> <DepartmentID>1</DepartmentID> <Name>Engineeringa</Name> <StartDate>2007-09-01T00:00:00</StartDate> </Department>
POCO kliens => szerviz:
<Department xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Administrator>2</Administrator> <Budget>350000.0000</Budget> <Courses/> <DepartmentID>1</DepartmentID> <Name>Engineeringaa</Name> <StartDate>2007-09-01T00:00:00</StartDate> </Department>
Self-tracking entity, szerviz => kliens:
<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<Administrator>2</Administrator>
<Budget>350000.0000</Budget>
<ChangeTracker z:Id="i2">
<ExtendedProperties/>
<ObjectsAddedToCollectionProperties/>
<ObjectsRemovedFromCollectionProperties/>
<OriginalValues/>
<State>Unchanged</State>
</ChangeTracker>
<Courses/>
<DepartmentID>1</DepartmentID>
<Name>Engineeringaaaaaaaa</Name>
<StartDate>2007-09-01T00:00:00</StartDate>
</Department>
Self-tracking entity, kliens => szerviz:
<Department z:Id="i1" xmlns="http://schemas.datacontract.org/2004/07/POCO1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<Administrator>2</Administrator>
<Budget>350000.0000</Budget>
<ChangeTracker z:Id="i2">
<ExtendedProperties/>
<ObjectsAddedToCollectionProperties/>
<ObjectsRemovedFromCollectionProperties/>
<OriginalValues/>
<State>Modified</State>
</ChangeTracker>
<Courses/>
<DepartmentID>1</DepartmentID>
<Name>Engineeringaaaaaaaaa</Name>
<StartDate>2007-09-01T00:00:00</StartDate>
</Department>
Érdekes, hogy a módosítás ténye csak a ST-ben látszik, még az eredeti entity-sben sem. A POCO-tól nem is vártuk persze.
Később még majd foglalkozok bővebben a témával. Aki játszani akar vele, hozza létre a School EF példaadatbázist, azon lehet futtatni.
Többen kérdeztétek, pptben benne vannak a linkek a térképes demóm adataihoz és a konverziós eszközökhöz is.
Mellékeltem még a Running Aggregate SQL és CLR példát (kurzorral is, azt nem mutattam), a trade riportot az új chartocskákkal.
Letöltés innen (4 M).
És végre gyorsan jön, de lehet, hogy csak azért, mert alszanak még Amerikában.
Szétestem az utóbbi két hónapban, annyi munkám volt (elnézést kis családomtól így nyilvánosan is), de most végre kicsit fellélegzek. Most sok időm lesz, elvégre a munkák mellé csak 6 MCP vizsgát raktam be áprilisra (.NET 4 béták). :)
Májusban viszont újra lesz Design Patterns tanfolyam, ahol az eddigi a Gof patternek mellett Unityről és általában a Dependency Injection frameworkökről, ViewModelről, Composite Application Block for WPF-ről, T4 template alapú kódgenerálásról, partial method alapú kibővíthetőségről és Entity Framework Portable Extensible Metadatáról (egyfajta DSL) is szó lesz.
Várok mindenkit szeretettel.
Aki a NetAcademiánál jelentkezéskor az alábbi C# kód által kiírt stringet bemutatja, 50e Ft kedvezményt kap az árból.
using System;
class Program
{
static void Main(string[] args)
{
Allat a = new Emlos();
a.Mozog();
}
}
class Allat
{
public virtual void Mozog()
{
Console.WriteLine("Állat vagyok, és mozgok");
}
}
class Emlos : Allat
{
public virtual void Mozog()
{
Console.WriteLine("Emlős vagyok, és mozgok");
}
}
Mostanában oktatok és prototípust írok, közben ezer design kérdést tisztázok a fejemben. Az egyik ilyen pl., hogy a ViewModel DependencyProperty vagy INotifyPropertyChanged módon közvetítse a változásokat a GUI-ra?
Én az INotifyPropertyChanged-re szavaznék, mert így a ViewModel és nem függ a GUI technológiától, eleve nekem fura a modellben pl. WPF fogalmakat látni.
Infók a kérdéskörben.
Általános áttekintés:
INotifyPropertyChanged vs. DependencyProperty in ViewModel
Egy POCO szavazat:
View Models: POCOs versus DependencyObjects
Én Expressionnel oldom meg, hogy ne legyenek property name stringek a kódban, ő nem, de az objektum pool ötlet tetszik benne, átveszem, de szemeteljünk.
A base class which implements INotifyPropertyChanged
Nagyon dicsérik a videót, még nem volt időm megnézni.
Jason Dolinger on Model-View-ViewModel
Egyféle Validation megközelítés.
Using a ViewModel to Provide Meaningful Validation Error Messages
Ez igencsak bosszantó volt, remélem a fix megoldja.
Nekem nem tablet pcm van, mégis előjön.
https://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=26662
A következőn töröm a fejem. Az Entity Framework SSDL-jében definiálva vannak az entitás property-k alapvető jellemzői: nullázhatóság, max hossz. Ezeket a GUI-n ki kell kényszeríteni. Nyilván vannak összetettebb validálási szabályok, de most koncentráljunk ezekre az elemiekre.
Utálok minden redundanciát egy rendszerben, ezért azt gondoltam, a szabályokat kiolvasom az EF sémájából, és ebből táplálom meg a validáló részeket, így nem kell törődni az egyszerű validálásokkal, automatikusan működni fognak.
A következő kis kódocska mutatja meg a metaadatok használatát:
o.ForceLoadingSchemas();
var sspaceEntitySets = o.MetadataWorkspace
.GetItems<EntityContainer>(DataSpace.SSpace)
.First().BaseEntitySets.OfType<EntitySet>();
foreach (EntitySet es in sspaceEntitySets)
{
foreach (EdmProperty p in es.ElementType.Properties)
{
ReadOnlyMetadataCollection<Facet> facets = p.TypeUsage.Facets;
Debug.WriteLine("{0} is {1} nullable", p.Name, (bool)facets["Nullable"].Value ? "" : "not");
if (facets.Contains("MaxLength"))
{
Debug.WriteLine("{0} MaxLenght is {1}", p.Name, (int)facets["MaxLength"].Value);
}
Debug.WriteLine("{0} is {1} nullable", p.Name, (bool)facets["Nullable"].Value ? "" : "not");
}
}
A ForceLoadingSchemas az ObjectContext partial classában van:
public void ForceLoadingSchemas()
{
CreateQuery<BusinessEntity>("AdventureWorks2008Entities3.BusinessEntities").ToTraceString();
}
Csinált már valaki ilyet? Van benne valami csapda, amit most nem látok?
Error: Incorrect syntax near valami.
Akkor jön elő, ha az SQLCLR assemblyt és benne a függvényeket akarja az VS deployolni. Több oka lehet, most az volt, hogy egy .NET oldalon double-t visszaadó függvény véletlenül így lett deklarálva:
[SqlFunction(..., TableDefinition = "Datum datetime, Szazalek double")]
Mi a hiba benne? SQL Serverben nincs double, csak real és float. Ráadásul a C# float az az SQL real és a C# double az SQL Server float (kb.). :)
Az előbbi helyesen:
[SqlFunction(..., TableDefinition = "Datum datetime, Szazalek float")]
Miért kellett SQLCLR függvényt írni? A futó aggregálások (én legalábbis nem tudok jobbat kurzor nélkül) o(n2)-es algoritmusok, ezt CLR-ben könnyen meg lehet írni o(n)-re. Pl:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class UserDefinedFunctions
{
internal class Result
{
public long RowId;
public double CumDollarGain;
public double TopDollarGain;
public double DollarDrawDown;
}
//Input table: create table #trades(RowId long, DollarGain money, other columns possible)
[SqlFunction(FillRowMethodName = "FillRow", DataAccess = DataAccessKind.Read,
TableDefinition = "RowId bigint, CumDollarGain float, TopDollarGain float, DollarDrawDown float")]
public static IEnumerable Cumul()
{
using (var conn = new SqlConnection("Context connection=true"))
{
using (var cmd = new SqlCommand("select RowID, DollarGain from #trades", conn))
{
var res = new List<Result>();
conn.Open();
double cumulPrice = 0, topPrice = 0, drawDawn = 0;
using (SqlDataReader r = cmd.ExecuteReader())
{
int idCol = r.GetOrdinal("RowID");
int gainCol = r.GetOrdinal("DollarGain");
while (r.Read())
{
var price = r.GetDouble(gainCol);
cumulPrice += price;
topPrice = Math.Max(price, topPrice);
drawDawn += price;
drawDawn = Math.Min(drawDawn, 0);
res.Add(new Result
{
RowId = r.GetInt64(idCol),
CumDollarGain = cumulPrice,
TopDollarGain = topPrice,
DollarDrawDown = drawDawn
});
}
return res;
}
}
}
}
public static void FillRow(object obj,
out long id,
out double cumDollarGain,
out double topDollarGain,
out double dollarDrawDown)
{
var r = (Result)obj;
id = r.RowId;
cumDollarGain = r.CumDollarGain;
topDollarGain = r.TopDollarGain;
dollarDrawDown = r.DollarDrawDown;
}
};
Sajnos nem lehet átpasszolni a megnyitott SqlDataReadert a két metódus között, ezért kénytelen az ember letárolni az eredményhalmazt. Persze pár ezer sornál ez nem gond.
Hogyan építsünk EF-re többrétegű appot?
1. Anti-Patterns To Avoid In N-Tier Applications
2. N-Tier Application Patterns
3. Building N-Tier Apps with EF4
Jó cikkek.
Na, ez aztán a furcsa. Pár napja írtam, mennyire lassan indul az EF. Nem tudom mitől, talán attól, hogy felraktam a Microsoft ADO.NET Entity Framework Feature Community Technology Preview 2-t, ami lecserélte a bugos .NET 4 Beta2-es EF assemblyket, amelyek nem használták ki az előfordított viewkat? Nem tudom, nem nyomoztam utána, mert most jó. :)
Korábban annyira felbosszantott, hogy elkezdtem nézni az NHibernate-et, de ahhoz meg a LINQ támogatás jár még gyerekcipőben, nekem meg tele van azzal a programom. Így az kiesett.
Marad az EF, így már nagyon szeretem.
Boldog Új Évet.
Ez az Entity Framework egy nehezen betörhető ló. A run-time teljesítménye jó, de az indulása, az katasztrófa.
Pár hasznos link a témában.
Alapelvek 1, 2.
Hol találhatunk fogást rajta?.
Viewk előre generálása:
Régi módon.
Eszköz hozzá: EdmGen.
Új módon:
How to use a T4 template for View Generation
Nagy modellek esetén hogyan ne őrüljünk meg (meg fogunk) 1, 2, gyakorlati példa.
A feladvány, hogy van sok szűrési feltétel, ezekre szűrni kell, ha van érvényes értékük, vagy kihagyni a szűrésből, ha nincs.
A feladatra SQL Server esetén bool algebrás megoldást írnék:
select * from tabla where (@p1 is null) or (@p1 = col1) and (@p2 is null) or (@p2 = col2) ... option(recompile)
Az option hint azért kell, mert így a nullos paraméterek teljesen kiesnek a tervből, csak a valódi szűrésekre készül terv. Ez szerintem óriási dolog, érdemes észben tartani.
Ugyanezt a logikát linqval is el lehet játszani, ref típusokkal kb. így:
var x = from z in Valami where (p1 == null) || (p1 = z.col1) && (p2 == null) || (p2 = z.col2)
Egy másik megoldásban azt használjuk ki, hogy késleltetett módon értékelődnek ki a kifejezések, így lehet őket láncolni. Itt látható ez a megoldás, és még más megközelítések is.
A harmadik megoldásban írhatunk egy saját szűrő operátort is erre a célra. Az előbbi címről:
public static IQueryable<TSource> WhereIf<TSource>(
this IQueryable<TSource> source, bool condition,
Expression<Func<TSource, bool>> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}
Nehéz megindokolni, melyik megoldás a jobb.
120 oldalas kis olvasmány, alig várom már, hogy legyen egy kis időm elolvasni.
Az tetszik a 4.0-ban, hogy végre megint hozzányúltak az alapokhoz, mint pl. a threadinghez.
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
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<BarCollectionDescriptor, BarFromTickFactory> FactoriesByDesc =
new Dictionary<BarCollectionDescriptor, BarFromTickFactory>();
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<int, List<BarFromTickFactory>> FactoryListBySymbolId =
new Dictionary<int, List<BarFromTickFactory>>();
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.
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á.
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.
Ezeket érdemes megnézni, érdekesek.
Ha nem szeretnénk, hogy egy rövid, de teljesítményzabáló kódunkat megszakítsa a GC: Latency Modes és Low-Latency GC in .NET 3.5.
Ha interopolunk, és várunk visszahívást nem managed oldalról, de félünk, hogy a GC felszabadítja az ojjektumunkat visszahívás előtt: GC.KeepAlive.
Ha nagyon sok memóriát zabáló appot írunk, és nem szeretnénk belefutni OutOfMemoryException-be, inkább lassítanánk a feldolgozáson: MemoryFailPoint.
1, 2, 3, 4. Nem csak a szokásos, közismert dolgok, hanem mélyebbek is.
Pl. tudod-e, mekkora memóriát kér el induláskor a GC az OS-től? Hogy 3-féle GC is van (2-t ismer a legtöbb ember, már, aki egyáltalán hallott róla, hogy többféle GC is létezik)? Vagy miért száll el általában egy x86-os .net app 1.5G körüli memóriafelhasználásnál? Stb.