.NET Performance Profiling

Transcription

.NET Performance Profiling
120dnp_Kochstudio_ws_la_ws_sb_ws.qxp:Layout 1
04.10.2010
11:53 Uhr
Seite 120
TIPPS & TRICKS
Kochen mit Patrick
.NET Performance Profiling
verhaltens eines Programms, meist mit dem Ziel
einer Verbesserung des jeweils gemessenen Kriteriums. Die meisten Werkzeuge erlauben entweder eine Messung der Performance oder der
Speicherausnutzung, wobei sich das Speicherverhalten durchaus auch negativ auf die Performance auswirken kann.
Profiler stehen nicht unbedingt ganz oben in
der Gunst der meisten Entwickler. Oft kommen
sie wie im fiktiven Beispiel erst bei echten Problemen zum Einsatz. Nichtdestotrotz oder gerade
deswegen gibt es durchaus einen Markt und ein
Angebot verschiedener Produkte. Eines davon
möchte ich Ihnen heute beispielhaft vorstellen.
Eqatec Profiler
Patrick A. Lorenz ist Geschäftsführer der PGK GmbH. Der auf .NET
spezialisierte Technologiedienstleister ist Anbieter des .NET-basierten
Content-Management-Systems
QualiSite. Daneben ist Patrick A.
Lorenz als Autor und Coach tätig. In
der Freizeit ist Patrick Hobbykoch.
Sie erreichen ihn unter www.pgk.de
oder unter [email protected].
dnpCode A1011Kochen
120
S
ie haben das bestimmt auch schon einmal
erlebt: Die Applikation für den Kunden ist
endlich in einer ersten Vorabversion entwickelt und im Testsystem installiert. Jetzt prüft
der Kunde sein neues Werkzeug und meldet neben diversen kleinen Fehlern und Verbesserungsvorschlägen vor allem eine miserable Performance. Eigene Tests bestätigen die Problematik, und es wird deutlich, dass die Entscheidung,
während der Entwicklung nur mit kleinen Datenmengen zu arbeiten, nicht die beste war. Was
soll’s, dem Kunden soll schließlich geholfen und
die Performance optimiert werden. Aber wie?
Performance-Probleme mit dem Debugger zu
identifizieren ist eine mühsame Arbeit. Die wichtigste Aufgabe einer genauen Lokalisierung der Ursache lässt sich damit nämlich kaum zielgerichtet
erfüllen. An dieser Stelle werden Sie vielleicht die
Suchmaschine Ihrer Gunst bemühen und sich in einer Selbsthilfegruppe – genannt Forum – mit anderen Betroffenen austauschen. Am Ende der Diskussion werden vermutlich einige Links zu finden sein,
die Sie zu Herstellern von Profiling-Tools führen.
Profiler ermöglichen die Messung, Protokollierung und anschließende Bewertung des Laufzeit-
Den Profiler von Eqatec [1] empfinde ich für den
Einstieg in die Thematik als sehr gelungen, da
das Produkt sehr intuitiv zu bedienen ist und innerhalb von wenigen Minuten erste verwertbare
Ergebnisse liefert. Außerdem bietet der Hersteller
eine leicht eingeschränkte Version kostenfrei an.
Ich habe zur Demonstration eine kleine Konsolenapplikation geschrieben, deren einzige Aufgabe darin besteht, aus einer Textdatei Semikolonseparierte Zahlenwerte zu extrahieren und in eine
zweite Datei zu schreiben. Das dauert für die
rund 10 000 enthaltenen Werte allerdings viel zu
lange: über 30 Sekunden. Erklärtes Ziel ist es, die
Schwachstellen zu identifizieren und zu optimieren. Auf geht’s!
Der Profiler ist ein von Visual Studio unabhängiges Werkzeug. Nach dem Start erwartet den Entwickler eine aufgeräumte Oberfläche bestehend
aus vier Registerreitern Build, Run, View und
Compare, wie in Abbildung 1 zu sehen. Funktional bauen diese Tabs aufeinander auf.
Zunächst muss die zu untersuchende Anwendung für das Profiling vorbereitet werden. Dazu
wird die Exe-Datei auf dem Reiter Build ausgewählt und per Klick auf den gleichnamigen Button
neu übersetzt. Was das konkret bedeutet, erkläre
ich später. An dieser Stelle sei aber noch erwähnt,
dass Sie weder die Quelltexte des zu untersuchenden Programms benötigen noch es sich zwangsweise um ein Debug-Build handeln muss. Nach
Möglichkeit sollte allerdings kein Obfuscator angewandt worden sein, und eine Signierung von relevanten Assemblies ist auch eher hinderlich.
Wurde die Applikation übersetzt, kann sie auf
dem zweiten Reiter Run ausgeführt werden. Im
Regelfall werden Sie nun die aus Anwendersicht
betroffenen Aktionen ausführen. Außer einigen
11.2010 www.dotnetpro.de
120dnp_Kochstudio_ws_la_ws_sb_ws.qxp:Layout 1
04.10.2010
11:53 Uhr
Seite 121
TIPPS & TRICKS
allgemeinen Einträgen findet sich anschließend im Protokoll des Profilers erst einmal
nichts Interessantes. Das können Sie mit
einem Klick auf Take snapshot ändern. Es
wird nun ein Profiling-Protokoll erzeugt,
das sich per Doppelklick in der Liste auf
dem dritten Reiter View einsehen lässt, vergleiche Abbildung 2. Mit der Erzeugung eines Snapshots werden alle Zähler bis zum
nächsten Snapshot zurückgesetzt.
Die Protokollansicht gliedert sich vertikal
in zwei Bereiche. Oben wird eine Liste aller
protokollierten Methoden angezeigt, bestehend aus dem Assembly-Namen sowie der
Parametersequenz zur Identifizierung von
Überladungen. Die Relevanz einer Methode für weitere Untersuchungen lässt sich
anhand mehrerer Spalten beurteilen:
❚ Total (Full) zeigt die kumulierte Aufrufdauer der Methode inklusive der von ihr
aufgerufenen Untermethoden in Millisekunden an.
❚ Avg (Full) gibt die durchschnittliche Aufrufdauer inklusive Untermethoden an.
❚ Total (self) und Avg (self) geben die korrespondierendenWerte an, jedoch nur für
die Methode selbst ohne Untermethoden.
❚ Calls benennt die Gesamtzahl der Aufrufe
der gewählten Methode.
Während die Gesamtaufrufzeit eher
peripher interessant ist, sollten Sie sich
Methoden mit einer hohen durchschnittlichen Durchlaufzeit etwas genauer anschauen, vor allem, wenn die Methode besonders häufig aufgerufen wird.
Durch Auswählen einer Methode in der
Liste wird diese im unteren Bereich als Box
visualisiert. Links von ihr stehen die aufrufenden Methoden und rechts die aufgerufenen. Aufgrund der geringen Komplexität
meiner Beispielapplikation lässt sich im Protokoll bereits auf den ersten Blick erkennen,
wo der Schuh drückt: Die Methode DumpNumberToFile wurde 9999 Mal aufgerufen
und hat jeweils 3,3 Millisekunden benötigt;
mit insgesamt über 32 Sekunden trägt sie
entscheidend zu der geringen Geschwindigkeit des Programms bei. Ein Blick auf den
Quelltext offenbart die Problematik:
private static void DumpNumbers(string numbers){
var numbersArray =
ConvertNumbersToArray(numbers);
foreach (var number in numbersArray) {
DumpNumber(number); }
}
private static void DumpNumber(string number) {
if (ParseNumber(number) != -1) {
DumpNumberToFile("parsednumbers.txt",
ParseNumber(number)); }
}
www.dotnetpro.de 11. 2010
[Abb. 1] Die Programmoberfläche von Eqatec Profiler.
[Abb. 2] Ansicht eines Profiling-Snapshots.
private static void DumpNumberToFile(
string fileName, int parsesNumber) {
using (var writer = File.AppendText(fileName))
{ writer.WriteLine(parsesNumber); }
}
Die Methode DumpNumbers wandelt
eine Zeichenkette in ein Array um und
übergibt dessen Elemente einzeln an die
Methode DumpNumber. Diese lässt die
Zeichenkette in eine Zahl umwandeln und
übergibt sie nach einer Prüfung an DumpNumberToFile. Hier wird eine Zieldatei geöffnet, die Zahl hineingeschrieben und die
Datei wieder geschlossen. Bei der beobachteten Anzahl von knapp 10 000 Aufrufen ist die Verzögerung einfach zu erklären.
Jetzt heißt es refaktorisieren. Im vorliegenden Fall sollte es ausreichen, den Dateiaufruf
aus der Methode DumpNumberToFile herauszuziehen und außerhalb der Schleife in
DumpNumbers zu hinterlegen. Der erhaltene StreamWriter wird als Parameter an die
Untermethoden übergeben:
private static void DumpNumbers(string numbers) {
var numbersArray = ConvertNumbersToArray(
numbers);
using (var writer = File.AppendText(
"parsednumbers.txt")) {
foreach (var number in numbersArray) {
DumpNumber(writer, number); }
}
}
Wenngleich die Chance einer Besserung
recht hoch ist, sollte diese erst noch bewiesen werden. Also heißt es erneut den Profiler vorbereiten und ein Protokoll anlegen.
Statt dieses wie zuvor manuell zu untersuchen, können Sie es mit dem vorherigen
Protokoll vergleichen lassen. Der Profiler
bemüht sich dabei, relevante Unterschiede
121
120dnp_Kochstudio_ws_la_ws_sb_ws.qxp:Layout 1
04.10.2010
11:53 Uhr
Seite 122
TIPPS & TRICKS
zu erkennen, was bei der vorliegenden Applikation recht einfach ist. Das Ergebnis
zeigt, dass die Methode DumpNumbers um
14 Prozent beschleunigt werden konnte.
Ihre Ausführungszeit liegt damit nun bei
3,1 statt 3,3 Millisekunden – eine enttäuschende Optimierung, denn die Gesamt-
laufzeit der Applikation beträgt immer
noch knapp 30 Sekunden.
Auf den zweiten Blick zeigt sich eine weitere Schwachstelle: Die Methode DumpNumbers wird knapp 11 000 Mal aufgerufen, und entsprechend häufig wird immer
die Zieldatei geöffnet. Im nächsten Schritt
habe ich den StreamWriter daher eine weitere Ebene nach oben verschoben und zudem sichergestellt, dass die Ausgangsdatei
nicht mehr zeilenweise, sondern am Stück
gelesen wird. Das hat gewirkt. Die Gesamtlaufzeit liegt nun bei insgesamt weit unter
einer Sekunde. Einen ähnlichen Fehler habe ich noch in der Methode DumpNumber
versteckt: Bestimmt finden Sie ihn!
Instrumentierung
[Abb. 3] Disassemblierter Code nach der Instrumentierung.
Es gibt unterschiedliche Ansätze, wie Profiler an ihre Daten kommen. Ein üblicher
Weg ist die Instrumentierung der zu untersuchenden Applikation. Dabei wird deren
Ausführungscode um Protokollfunktionen
ergänzt, meist zu Beginn jeder einzelnen
Methode sowie vor deren Verlassen. Aus
diesen Werten lassen sich die notwendigen
Aufrufzeiten sehr einfach errechnen.
Auch Eqatec instrumentiert die zu optimierende Applikation. Im beschriebenen
Build-Schritt werden die ausgewählten Assemblies einer Applikation um eine Referenz
auf die Eqatec-Runtime ergänzt und die notwendigen Protokollaufrufe integriert. Transparent lässt sich dieses Vorgehen mit einem
Disassembler wie Reflector machen. In Abbildung 3 ist eine der instrumentierten Methoden abgedruckt, die vom Profiler ergänzten Aufrufe sind deutlich zu erkennen.
Durch die Instrumentierung vergrößert
sich nicht nur das Programm, sondern es
verlangsamt sich auch dessen Ausführung.
Für die Identifizierung von Problemen ist
dies aber meist nicht relevant. Ein anderer
Umstand des Ansatzes wiegt dabei schon
schwerer: Nicht instrumentierter Code
kann nicht gemessen werden. Aufrufe in die
.NET Framework Class Library können
nicht gemessen werden. Was immer sie also
unterhalb von System.* aufrufen, bleibt dem
Profiler verborgen. Der Instrumentierung
gegenüber stehen andere Ansätze wie das
Sampling, das ohne Eingriff in den Code
auskommt, dafür jedoch nur ungenauere
Daten liefert. Speziell basierend auf dem
JIT-Compiler von .NET existiert ein dediziertes .NET-Profiling-API. Dieses ermöglicht es Werkzeugen, auf Ebene der CLR Interesse an Profiling-Informationen anzumelden. Die notwendigen Instrumentierungsaufrufe müssen dann nicht mehr in
die Applikation selbst integriert werden,
sondern werden über die CLR geliefert [2].
ASP.NET und andere Typen
[Abb. 4] Snapshot des integrierten Visual Studio Profilers.
122
Der Profiler von Eqatec unterstützt Desktopund Silverlight-Applikationen sowie Windows-Dienste. Mobile Anwendungen, die
11. 2010 www.dotnetpro.de
120dnp_Kochstudio_ws_la_ws_sb_ws.qxp:Layout 1
04.10.2010
11:53 Uhr
Seite 123
TIPPS & TRICKS
Das ist alles nur geklaut …
… das ist alles gar nicht meine! Gibt es die Prinzen eigentlich noch?
Dieses Rezept stammt aus dem Rezepte-Wiki [1] aus der Feder eines
unbekannten Gourmets. Danke, schmeckt echt lecker und
wird hoffentlich nachgekocht!
Wraps Asia-Style
In diese Wraps kommen zur Abwechslung mal keine
Kidney-Bohnen, sondern eine asiatische Variante.
Geben Sie jeweils etwa 75 Milliliter gute Sojasauce
sowie Reiswein (Sake) in eine große Schüssel und mischen
Sie einen Esslöffel braunen Zucker darunter. Waschen und
schneiden Sie 500 Gramm Hähnchenbrustfilet in Streifen und
geben Sie sie unter die Marinade. Jetzt muss das Ganze einige
Stunden oder besser noch über Nacht im Kühlschrank marinieren.
Sobald es ernst wird, schneiden Sie etwas Rotkohl sowie etwas Blattsalat
in Streifen. Die kommen zusammen mit Bambussprossen und einer in Stifte
geschnittenen Möhre in eine große Schüssel.
Rösten Sie etwas Sesam sowie Schwarzkümmel in einer Pfanne ohne Fett an – wenn Sie
mögen, auch noch einige Cashewkerne. Lassen Sie nun die Marinade vom Fleisch abtropfen und braten Sie es mit zwei Esslöffeln Sesamöl kräftig in
der Pfanne an. Parallel wärmen Sie einige Weizentortillas entsprechend den Anweisungen auf der Verpackung auf.
Damit es nicht zu trocken wird, streichen Sie auf die Tortillas jeweils etwas Joghurt. Sie können ihn auch mit einem kleinen Schuss Sojasauce und
etwas Minze und Koriander veredeln.
Das Fleisch wird mit den Veggie-Zutaten vermischt, auf den Tortillas verteilt, noch ein kleiner Klecks von der Joghurtsauce darüber und anschließend
zu Wraps falten. Eine Anleitung dazu sollte sich ebenfalls auf der Verpackung finden. Das war’s – guten Appetit!
[1] Rezepte-Wiki, www.rezeptewiki.org
mit dem .NET Compact Framework entwickelt wurden, lassen sich ebenfalls untersuchen, und zwar sowohl im Emulator als auch
in einem angeschlossenen Gerät.
Laut Aussage des Herstellers weniger gut
unterstützt wird ASP.NET. Gemäß den Aussagen der Support-Abteilung nutzen nur
rund drei Prozent der Anwender das Werkzeug zur Optimierung von Webapplikationen. Möglich ist es indes sehr wohl, und
auch die Ergebnisse sind durchaus hilfreich. Das Vorgehen ist allerdings minimal
weniger komfortabel :
❚ Wählen Sie im Profiler zunächst das binVerzeichnis der bereits kompiliertenWebapplikation und führen Sie die Instrumentierung aus.
www.dotnetpro.de 11. 2010
❚ Starten Sie nun die Webapplikation und
führen Sie die relevanten Aktionen aus.
Im Profiler können Sie wie gewohnt
Snapshots anlegen und auswerten.
Das Profiler-API
Eine interessante Option des Eqatec Profilers ist sein API, mit dem aus der untersuchten Applikation auf den Profiling-Prozess
eingewirkt werden kann. So können Sie
Methoden oder ganze Klassen mittels einer
Attribut-Kennzeichnung ausblenden (HideAtRuntime) oder ganz vom Profiling ausschließen (SkipInstrumentation). Dazu referenzieren Sie zunächst die Assembly
EQATEC.Profiler.RuntimeFullNet.dll und
fügen dann die gewünschten Attribute ein:
[HideAtRuntime]
private static int ParseNumber(string number)
{ ... }
Auch in eine aktive Profiling-Session können Sie eingreifen. So ermitteln die folgenden Zeilen über statische Eigenschaften und
Methoden der Klasse Runtime, ob die Anwendung gerade in einer Session läuft, und
erzeugen gegebenenfalls einen Snapshot:
if (Runtime.IsProfiled) {
Runtime.TakeProfileSnapshot();
}
Performance Profiling
Wenn Sie Visual Studio Premium oder Ultimate Ihr Eigen nennen, dann bietet Ihnen
die Entwicklungsumgebung bereits inte-
123
120dnp_Kochstudio_ws_la_ws_sb_ws.qxp:Layout 1
04.10.2010
11:53 Uhr
Seite 124
TIPPS & TRICKS
grierte Profiling-Werkzeuge. Sie finden diese im Menü Analyzer. Mithilfe eines Assistenten können Sie direkt in Visual Studio
das Profiling konfigurieren. Neben der Datenbeschaffung mittels Instrumentierung
können Sie auch Sampling nutzen. Außerdem bietet Visual Studio Memory- und
Concurrency-Profiling an.
In Abbildung 4 ist ein mitVisual Studio erzeugter Performance Profile Snapshot abgedruckt. Die Daten wurden wie beim Produkt
von Eqatec per Instrumentierung gewonnen. Die Auswertungen gehen im Vergleich
deutlich weiter, sind für den Einstieg dadurch aber auch schwieriger zu deuten. Ein
echtes Highlight ist die integrierte Code-Ansicht, die ein Wechseln zwischen Profiler
und IDE unnötig macht.
[Abb. 5] Snapshot der Testapplikation in dotTrace.
Alternativen
Neben den beiden vorgestellten Werkzeugen möchte ich ein drittes zumindest
erwähnen: dotTrace Performance von JetBrains [3]. Das Tool ist unabhängig von
Visual Studio, integriert sich dort jedoch
optional. Ähnlich wie bei dem in Visual
Studio integrierten Profiler ist die Protokollierung sehr umfangreich und lässt sich auf
unterschiedlichen Wegen sammeln. Abbildung 5 zeigt zum Vergleich einen Snapshot
der Testapplikation.
Fazit
Machen Sie’s besser als in der fiktiven Geschichte zu Beginn. Statt die Applikation
erst nach dem Ausliefern auf PerformanceProbleme hin zu untersuchen, können Sie
dies als Qualitätssicherung bereits frühzeitig in den Entwicklungsprozess integrieren.
Wer Visual Studio Premium oder Ultimate verwendet, der wird sicher zuerst den
integrierten Profiler ausprobieren wollen.
Bei den anderen Editionen kann auf Pro-
dukte anderer Hersteller zurückgegriffen
werden. Für den Einstieg zu empfehlen ist
ein einfach gehaltener Profiler wie der von
Eqatec. Wer sich schon auskennt, ist bei
[bl]
dotTrace gut aufgehoben.
[1] Eqatec Profiler,
www.dotnetpro.de/SL1011Kochstudio1
[2] The .NET Profiling API,
www.dotnetpro.de/SL1011Kochstudio2
[3] dotTrace, www.jetbrains.com/profiler
.NET Extensions
Standardwert ermitteln
Seit dem letzten Release wurde das Projekt wieder um eine Reihe von Erweiterungsmethoden ergänzt.
Für DateTime-Datentypen können Sie nun beispielsweise prüfen, ob nur Tag oder Zeit einem übergebenen Wert entsprechen. Für alle Typen (System.Object) können Sie nun unter anderem den Standardwert
des Datentyps ermitteln (GetTypeDefaultValue). Für alle Wertetypen (int, DateTime, Guid et cetera) stehen die neuen Methoden IsEmpty, IsNotEmpty und ToNullable zur Verwendung bereit.
Das laufend aktualisierte .NET-Extensions-Projekt
der dotnetpro finden Sie unter [1]. Die hier beschriebene aktuelle Version hat die Release-Nummer 2010-11.
Ebenfalls unter [1] ist eine Liste aller bereits verfügbaren Erweiterungsmethoden zu finden. Die Quelltexte
können Sie mithilfe des Versionsverwaltungssystems
[bl]
Subversion [2] unter [3] auschecken.
[1] Projekt-Website, http://extensions.dotnetpro.de/
[2] Subversion, http://subversion.apache.org/
[3] Quelltexte per SVN,
https://dnpextensions.svn.codeplex.com/svn
124
11. 2010 www.dotnetpro.de