.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