maandag 1 september 2008

Team Coherence hack -> 10x sneller!

Op mijn huidige detacheringsplek gebruiken ze "Team Coherence" (http://www.teamcoherence.com) voor versie beheer en bug/issue tracking. Ze gebruiken dit pakket heel strict: bijvoorbeeld elke check-in moet een tracker melding hebben, een tracker heeft aantal stadia (waaronder controle door de aanvrager of indiener), versie labels, promotion levels, etc.

Op zich zijn ze tevreden over dit pakket. Het pakket heeft wel een poos een dip gehad, waarbij het niet meer onderhouden werd door de makers, maar nu wordt weer af en toe een update uitgebracht. Het grootste probleem echter wat ze hadden, was dat het pakket steeds trager werd: opstarten duurde maar liefst anderhalve minuut! Ook wisselen van views etc duurde dik een minuut.

Toen ik even niets te doen had (wachten op specs etc), heb ik even gekeken met Proces Explorer naar wat de oorzaak kon zijn. Nu blijkt dat hij veel geheugen acties doet (van 10Mb oplopend naar 65Mb en dan weer 36Mb), en veel kernel/system (rood) tijd gebruikt. Zie onder (er is trouwens getest op een HT systeem, vandaar niet meer dan 50% CPU):


1 thead (main thread) is druk bezig. Zie onder:



Als we nu de stack trace opvragen (en dit een paar keer doen), dan blijkt dat hij steeds bezig is met "GlobalRealloc".
Zie onder:


Ik weet dat het een Delphi 5 programma is, en Delphi gebruikt maar op 1 plek Global memory: TMemorystream.SetSize. Waarschijnlijk wordt steeds een paar bytes geschreven (stream.Write(s) oid), zodat het geheugenblok steeds met paar bytes vergroot moet worden. Nu doet Windows dit niet efficiënt: zelfs als na het huidige blok genoeg ruimte is, hij kopieert altijd naar een nieuw blok geheugen. En dit kost tijd, vooral als het heel vaak gebeurt. Vandaar de "rode" systeem CPU tijd.

Nu heb ik al wat ervaring met hacken gekregen met mijn profiler (http://code.google.com/p/asmprofiler) en detouring, dus ik heb ik een kleine dll geschreven die de GlobalRealloc overruled, en 1Mb extra reserveert. Als de nieuwe ruimte nog past in het oude, dan doet het niets, anders vraagt het door middel van de originele GlobalRealloc nieuw geheugen aan (met weer 1Mb extra):

function HookedGlobalReAlloc(hMem: HGLOBAL; dwBytes: SIZE_T; uFlags: UINT): HGLOBAL; stdcall;
begin
if GlobalSize(hMem) < id="pcex4"> Result := trampoline_GlobalReAlloc(hMem, dwBytes + (1000*1024), uFlags) //reserve 1Mb extra mem
else
Result := hMem; //do not realloc if needed size fits in current size, GlobalRealloc ALWAYS does a resize, even when it is not needed!
end;

Door middel van dll injection heb ik dit in Team Coherence geladen (zowel server als client), en nu duurt het opstarten maar 10 seconden! Dus 10x sneller!

P.S. ik heb dit ook aan de makers gestuurd, nog geen reactie tot nu toe...
P.S.2 de volgende keer zal ik demonstreren hoe ik de executable aangepast heb, zodat altijd mijn extra dll geladen wordt (dll injection dus niet meer nodig elke keer).

dinsdag 19 augustus 2008

ADO = Traag, kan 2x sneller!

Ik had wel eens eerder gehoord dat ADO traag zou zijn, maar ik had met ADO nog geen ervaring.
(trouwens, deze traagheid geldt voor ADO Win32, ADO.net weet ik niet, maar als ik SQL Server Enterprise Manager 2003 (win32)
vergelijk met SQL Server Management Studio 2005 (.net) dan is de .Net versie VELE malen trager!)

Geen ervaring, tot nu toe, want bij mijn huidige opdracht hebben ze een aantal database performance problemen.
De performance problemen komen oa door een slecht ontwerp (elke cell in een grid/lijst is een object
met allerlei properties -> veel overhead), maar ook voor een deel door ADO. De data wordt namelijk
via ADO geladen in hun eigen data objecten.

Nu verloopt bij ADO alles via OLE interfaces. Elke call moet worden geconverteerd, gecontrolleerd, foutafhandeling ,etc.
Dit geeft veel overhead. En bij het laden van data gebeurt dit bij elke cell! Als je veel records met veel velden hebt, gaat het dus hard...
Om deze overhead te minimaliseren kun je zelf ook gebruik maken van de interfaces ipv bijv. de standaard ADO dataset van Delphi.
Dit kan het ongeveer 2x sneller maken! Vooral bij veel enkelvoudige queries (detail record van een ID ophalen).

Voorbeeld:

//eerst data laden
procedure LoadData(aSQL: string);
var
connection : TADOConnection;
recordset : _Recordset;
dataset : TADODataSet;
begin
//recordset bevat data van SQL statement (select * from bla)
recordset := connection.Execute( aSQL );

//of van (bestaande) dataset
dataset.CommandText := aSQL;
dataset.Open;
recordset := dataset.RecordSet;
end;

//dan data vullen in een data object oid
procedure FillData;
var
field : TField;
adofield : ADOInt.Field;
recordset : ADOInt._Recordset
i : integer;
olerows : OleVariant;
begin
for i := 0 to recordset.Fields.Count-1 do
begin
//store adofield reference
adofield := recordset.Fields[i];
//get field object by name
f := data.FieldByName( adofield.Name );

//get value
f.Value := adofield.Value;

//of
//vanaf ADO 2.5 30% sneller?
f.Value := recordset.Collect[i];

end;

//of: alle waarden van record (of meerdere regels) in een array:
//get all values of current row
olerows := recordset.GetRows(1, 0, EmptyParam);

for i := 0 to recordset.Fields.Count-1 do
//niet sneller maar iets trager?
f.Value := olerows[i, 0];
end;

PS: eventueel de waarde van OleVariant nog omzetten naar juiste type Variant dmv "VarAsType", want met
OLE kan bijvoorbeeld een datum of een decimaal als een intern type opgeslagen zijn.

vrijdag 13 juni 2008

Reflector + Reflexil: Verander de code van een assembly (!)

".Net Reflector" zullen de meeste .Net'ers wel kennen (.Net assemblies details bekijken, analyseren, decompilen, etc):
http://www.aisto.com/roeder/dotnet/Download.aspx?File=Reflector
Voor dit mooie programma zijn ook allerlei "add ins" beschikbaar:
http://www.codeplex.com/reflectoraddins

1 ervan is "Reflexil":
http://sebastien.lebreton.free.fr/reflexil/
Hiermee kan een assembly ook bewerkt worden! Bestaande code kan aangepast worden, of geheel vervangen worden door nieuwe code. Ik heb het zelf even getest met een Delphi.Net programma, en het werkt super.

Het is gebasseerd op Mono.Cecil, wat onderdeel is van het open source "Mono" project:
http://www.mono-project.com/Cecil

Een aantal voorbeelden hoe je een assembly kunt aanpassen:
http://www.codeproject.com/KB/msil/reflexil.aspx
http://blog.cumps.be/reverse-engineering-with-reflector-and-reflexil/

Wat wel even belangrijk is als je het zelf probeert: als je iets aangepast hebt, moet je in de tree van Reflector de assembly zelf selecteren. Dan pas kun je de wijzigingen opslaan :-).

Nu kan het een en ander natuurlijk moeilijker gemaakt worden dmv "obfuscation". Hiermee worden de originele namen vervangen door "abc", code structuur iets aangepast (geen mooie "if then else" constructies, etc):
http://blog.cumps.be/obfuscation-making-reverse-engineering-harder/

Ook kun je "Code Signing" gebruiken om het nog moeilijker te maken, maar het is en blijft vrij eenvoudig mogelijk om een assembly aan te passen:
http://blog.cumps.be/code-signing-as-reverse-engineering-protection/

Veel succes ermee! :-)

vrijdag 6 juni 2008

Snelheid + Schaalbaarheid .Net & Delphi

Als Delphi programmeur was ik nog een beetje sceptisch over .Net, vooral mbt de snelheid. Hiervoor heb ik in Delphi een klein programmaatje gemaakt, wat in 2 for loops wat strings heen en weer kopieert. Dit heb ik gemaakt in een thread, zodat ik ook makkelijk met meerdere threads kon testen. Dit programmaatje heb ik met Delphi 2006 zowel native als met Delphi.Net gecompileerd.

Test resultaten: Win32 vs .Net
Resultaten op een Intel Core2 Duo (dual core dus), MS .Net 2.0:
Delphi.Win32:
1 thread = 3s
2 threads = 6s + 6s

Delphi.Net:
1 thread = 2s
2 threads = 4s + 4s

.Net is dus sneller dan native! Bovendien schalen ze allebei niet goed, want beide keren bleef de CPU steken op zo'n 50% (dus geen goed gebruik van dual core).

Test resultaten: TopMM
Standaard gebruikt Delphi 200X de FastMM memory manager. Ik had al eens eerder een andere gezien, die wel goed zou moeten schalen: TopMM (http://topsoftwaresite.nl/Downloads/TopMemoryManager22.zip).

Resultaten:
Delphi.Win32, TopMM:
1 thread = 6s
2 threads = 6s + 6s

Tja, nu schaalt hij heel mooi (beide cores op 100%) maar de performance is wel slechter...

FastMM fix
Benieuwd waarom TopMM wel goed schaalt en FastMM niet, zat ik in de code te duiken. FastMM gebruikt een lock voor de memory pool, terwijl TopMM per thread een aparte pool heeft (geen locks dus nodig). Als eerste heb ik de lock eruit gehaald: nu kwam de snelheid ook op 2s! Maar goed, voor meerdere threads is die lock wel nodig. Toen heb ik even snel een hack gemaakt voor FastMM door oa een "threadvar" te gebruiken, zodat elke thread zijn eigen pool krijgt. Nu hebben beide threads 2s en 100% CPU, oftewel: Delphi.Win32 is even snel als .Net, maar bovenal: het schaalt beter!

Multi cores: nadeel voor .Net
Het grote nadeel nu aan .Net is dat je dit niet makkelijk zelf kunt fixen, wat in Delphi.Win32 wel makkelijk kan. Vooral met het oog op steeds meer cores per CPU is dit een slecht punt voor MS .Net.

MS CLR shared code
Benieuw hoe in .Net oa de string allocatie is, kwam ik via het volgende artikel:
http://selvasamuel.wordpress.com/2008/03/14/boxingunboxing-in-net/
op het idee om in de CLR code van Microsoft te duiken. Via het "Shared source" initiatief van MS kan iedereen een gedeelte van oa de CLR downloaden (maar niet aanpassen etc!):

Shared Source Common Language Infrastructure 2.0 Release
http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en

Als eerste heb ik in de ".Net Reflector" (http://www.aisto.com/roeder/dotnet/Download.aspx?File=Reflector)
de code opgezocht van mijn thread. Aan de hand hiervan naar de functie gesprongen (door te klikken in de code) voor het kopiëren van strings: System.String.Copy. Deze maakt oa gebruik van "FastAllocateString":
Deze functie was als volgt gedeclareerd (in C#):
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string FastAllocateString(int length);

"InterCall" betekent dat het in de .Net CLR zelf geïmplementeerd is. Aan de hand van de shared code van MS van de CLR heb ik gezocht op "FastAllocateString". Deze functie wordt weer doorgelinkt van "ECall::FastAllocateString" naar "JIT_TrialAlloc::GenAllocString(flags)".
Hierin wordt net als FastMM een lock gebruikt. Helaas is deze lock niet makkelijk aan te fixen, oa omdat de Shared Source CLR niet compleet is.

vrijdag 21 december 2007

AsmProfiler

Ontstaan

AsmProfiler is ontstaan naar aanleiding van mijn ontevredenheid over de profilers die ik gebruikt heb. Ik wilde oa meer details zien, en het moest makkelijk en snel in gebruik zijn. Via een oud-collega (Thaddy, nog bedankt!) kwam ik in aanraking met "detouring". Hiermee kon ik echter nog niet direct een profiler mee maken. Daarvoor heb ik zelf dmv assembly wat meer werk moeten doen. Dit gelukkig gelukt: mijn "proof of concept" werkte!

Werkbare versie, maar nog genoeg te doen...

In het afgelopen jaar heb ik in mijn vrije tijd het concept uitgebreid tot een redelijk werkbare versie. Nog lang al mijn ideeen zijn er niet in verwerkt, dus ik hoop nog veel functionaliteit toe te voegen! Verder is de documentatie nog niet in orde, en de code moet gerefactored/opgeschoond worden (ik heb al veel concept en "hackiediehack" :-) code verbeterd).

Open source

Ik heb het ontwikkeld met de "open source" gedachte: Ik heb zelf te weinig tijd om alles zelf te programmeren, en zelf vind ik dat elke programmeur zonder gedoe een profiler moet kunnen gebruiken. Dus bij deze wil ik iedereen oproepen om mee te helpen met het programmeren :-) .

Google Code

Het project heb ik bij "Google Code" gehost: http://code.google.com/p/asmprofiler/
Daar staan oa een wikki, issue tracker, download list, etc. Tevens regelt Google de opslag etc door middel van "Subversion" als versie beheer systeem. Lekker makkelijk voor mij dus. Wil je mee helpen met de code, dan zul je waarschijnlijk eerst moeten aanmelden, en ik moet je toegang geven tot mijn project. Maar dat zie ik dan wel weer, als er iemand is die belangstelling heeft :-) .

Asmprofiler demo

Ik heb naar aanleiding van een "klacht" op de DDD gelijk een klein demo project gemaakt :-) , dat toont hoe je AsmProfiler in een willekeurig Delphi project kunt gebruiken: http://asmprofiler.googlecode.com/files/DllLoadDemo.zip
http://asmprofiler.googlecode.com/files/DllTestApps.zip

Met C en C++ is het ook gewoon mogelijk om de dll te laden, hier heb ik echter geen C header file oid voor gemaakt. Die moet je zelf even maken adhv de Delphi interface unit.

Dll injection

Als je je programma niet wilt aanpassen, of als je de profiler wilt gebruiken op een programma waar je de code niet van hebt. dan kun de je de "injection" versie gebruiken: http://asmprofiler.googlecode.com/files/AsmProfiler_inject_v10011.zip

Start hiervoor de command line tool "inject.exe", zoek via de "Windows Task Manager" of "Process Explorer" de PID van een programma op, en voer die in. Druk op enter, en de dll wordt via officieele Windows APIs in het programma geinjecteerd :-) . Het scherm van de profiler wordt getoond zodra het geinjecteerde programma "GetMessage" of "Peekmessage" aanroept (normale Windows message loop). Echter, bij sommige programma's werkt dit niet, zoals Open Office.

Gebruik

Als je de profiler dll geladen hebt en het profiler scherm ziet, dan moet je eerst aangeven welke units en functies je wilt profilen door op de "Select items" knop te drukken:

profiler.PNG

Je krijgt dan een scherm te zien met alle units uit de .map file (Delphi -> Project options -> Linker -> Map file -> Detailed). Je kunt ook kiezen om alle (of bepaalde) functies van een geladen dll te selecteren:

selectitems.PNG

Door op "Start" te drukken wordt het profilen gestart, de "Stop" knop stopt het monitoren :-) . Vervolgens kun je via "Show results" de resultaten bekijken:

results.PNG

Dubbelklikken op een item gaat naar het 2e tabblad met meer details.

Disclaimer

Uiteraard is het gebruik volledig op eigen risico :-) . Normaal gesproken moet het geen problemen geven, maar van bepaalde units weet ik dat sommige low level Delphi functies crashes veroorzaken... Zoals de "classes" unit bevat ook adressen naar (het begin van?) classes zelf ipv de functies van classes. Dit moet ik nog uitzoeken. Verder kun je beter de "system" unit ook vermijden: deze bevat veel low level functies. Het beste kun je je "eigen" units gebruiken, en de "Windows" unit is handig omdat het veel standaard Windows APIs bevat.

donderdag 6 december 2007

Delphi speed tips

Na mijn ".Net speed tips" kon een Delphi versie natuurlijk niet uitblijven :-).
Dus bij deze de eerste tips, een tweede (of meer) komen later.

Delphi IDE
De IDE van Delphi kan op een aantal manier versneld worden. Dit betreft de werking van IDE zelf, geen shortcuts etc.

- FastMM memory manager replacement voor Delphi
(http://sourceforge.net/projects/fastmm/)

De makers van FastMM hebben voor Delphi een "replacement dll" gemaakt, die het geheugen beheer van Delphi overneemt. Dit geldt dus alleen voor de Delphi IDE zelf, niet de gecompileerde applicaties (zie onder). Door "borlndmm.dll" te vervangen door de FastMM versie, is de IDE sneller en stabieler (!). Werkt van D5 tot D2005 (D2006 en hoger gebruiken zelf standaard al FastMM!).

- DelphiSpeedUp plugin
(http://andy.jgknet.de/dspeedup/)

Met deze plugin wordt de werking van de Delphi IDE een stuk versneld. Onder andere de opstarttijd is een stuk sneller, maar ook de algehele werking is sneller, doordat interne functies vervangen worden door functies van het Fastcode project (in geheugen, de Delphi .exe wordt niet aangepast). Verder heeft het allerlei interne caches en andere optimalisaties voor een snellere werking. Een echte aanrader!

Delphi experts
Delphi experts zijn plugins waarmee het leven van een programmeur gemakkelijk wordt :-).
Een kleine opsomming van mijn favorieten:

GExperts
(http://www.gexperts.org/tour/)

Dit is een zeer uitgebreide plugin, met zeer veel handige functies. De "experts" zijn verdeeld
over een aantal categorieen:
"Editor experts" - Bijvoorbeeld regels sorteren, reverse statement (!), previous/next identifier (begin/end etc).
"IDE enhancements" - Zoals multiline tabs, component palette verbeteringen.
"Code editor enhancements" - Multiline tabs voor open bestanden, editor toolbar.
"Editor toolbar" - Hierop kun je allerlei functies op plaatsen.

De extra functies die GExperts biedt lopen uiteen van een ASCII tabel tot een zeer goede "Grep
search", van "Set tab order" tot "replace components", van "Project option sets" tot "Project dependencies, van "Clipboard history" tot "Executable information". Verder gebruik ik de "Procedure list" functie erg vaak: CTRL-G indrukken en je krijgt een lijst van alle functies
die een Delphi unit bevat. Default is de subsearch actief: door een gedeelte van een naam in te typen wordt de lijst gefilterd, via "enter" wordt naar de functie gesprongen. Ideaal!

DDevExtensions plugin
(http://andy.jgknet.de/dspeedup/index.php?page=DDevExtensions)

Van de maker van de "DelphiSpeedUp plugin". Bevat een aantal kleine verbeteringen zoals een simpele progressbar tijdens compileren en een "component selector". Maar het meest gebruik ik de sterk verbeterde "View unit" en "View form" functies. Deze werken net als de "Procedure list" functie van GExperts. In dit geval gebruik je de normale CTRL-F12 en SHIFT-F12 sneltoetsen. In plaats van een simpele lijst waarin je alleen op het begin kunt zoeken, krijg je een mooie lijst die je kunt sorteren, filteren op classe, etc. Maar vooral super werkt subsearch: door een gedeelte van een unit/form naam in te typen, waardoor de lijst gefiltert wordt. Ideaal!

JEDI Experts
(http://homepages.codegear.com/jedi/jvcl/)

Deze zijn onderdeel van de JEDI componenten (JVCL, JCL). Deze bevatten een aantal debugger plugins (auto insert JEDI debug data in .exe, threadnames, etc), Project analyzer, Uses wizard etc.

.Net speed tips

Hoewel ik zelf (nog) geen ervaring heb met .Net, vindt ik optimalisaties altijd interresant,
maakt niet uit van wat. Aangezien het officieel .Net blog is (qua naam), kan ik op deze
manier ook wat meer .Net gerelateerde content plaatsen :-).

Speeding Up .NET
(allerlei kleine tips voor snelheidswinst)
http://www.codeguru.com/vb/gen/vb_misc/tips/article.php/c14049/

Strings UNDOCUMENTED
(StringBuilder efficienter dan Strings)
http://www.codeproject.com/dotnet/strings.asp

Arrays UNDOCUMENTED
(waarom one-dimensional arrays sneller zijn dan multi-dimensional)
http://www.codeproject.com/dotnet/arrays.asp

Wat specifieker, mbt Serialization:

Optimizing Serialization in .NET, part 1
http://www.codeproject.com/dotnet/FastSerializer.asp
Optimizing Serialization in .NET, part 2
http://www.codeproject.com/dotnet/OptimizingSerialization2.asp