Übersicht über die Profilerstellung

Ein Profiler ist ein Tool, das die Ausführung einer anderen Anwendung überwacht. Ein Common Language Runtime (CLR)-Profiler ist eine Dynamic Link Library (DLL), die aus Funktionen besteht, die mithilfe der Profilerstellungs-API Meldungen von der CLR empfangen und an diese senden. Die Profiler-DLL wird zur Laufzeit von der CLR geladen.

Herkömmliche Profilerstellungstools dienen vorwiegend dazu, die Ausführung der Anwendung zu messen. Das bedeutet, dass sie die für jede Funktion aufgebrachte Zeit und die Speicherauslastung der Anwendung über einen bestimmten Zeitraum messen. Die Profilerstellungs-API zielt auf eine breitere Klasse von Diagnosetools ab, z. B. Dienstprogramme zur Codeabdeckung und sogar erweiterte Debughilfen. Diese Verwendungsmöglichkeiten sind ausnahmslos von diagnostischer Natur. Die Profilerstellungs-API misst nicht nur die Ausführung einer Anwendung, sondern überwacht sie auch. Aus diesem Grund sollte die Profilerstellungs-API nie von der Anwendung selbst verwendet werden, und die Ausführung der Anwendung sollte nicht vom Profiler abhängen (oder davon beeinflusst werden).

Die Profilerstellung für eine CLR-Anwendung erfordert eine weiter reichende Unterstützung als die Profilerstellung für Computercode, der auf herkömmliche Weise kompiliert wurde. Dies liegt daran, dass die CLR Konzepte wie Anwendung Standard s, Garbage Collection, verwaltete Ausnahmebehandlung, Just-in-Time(JIT)-Kompilierung von Code (Konvertieren allgemeiner Zwischensprache oder CIL, Code in systemeigenem Computercode) und ähnliche Features einführt. Herkömmliche Profilerstellungsmechanismen können keine nützlichen Informationen über diese Funktionen identifizieren oder bereitstellen. Die Profilerstellungs-API liefert diese fehlenden Informationen hingegen in effizienter Weise und mit minimalen Auswirkungen auf die Leistung der CLR und die Anwendung, für die das Profil erstellt wird.

Die JIT-Kompilierung zur Laufzeit bietet hervorragende Möglichkeiten zur Profilerstellung. Die Profilerstellungs-API ermöglicht es einem Profiler, den Speicher-CIL-Codestream für eine Routine zu ändern, bevor er JIT-kompiliert wird. Auf diese Weise kann der Profiler bestimmten Routinen, die genauer überprüft werden müssen, Instrumentierungscode dynamisch hinzufügen. Dieser Ansatz ist zwar auch in herkömmlichen Szenarios möglich, lässt sich aber für die CLR mit der Profilerstellungs-API wesentlich einfacher umsetzen.

Die Profilerstellungs-API

Wird die Profilerstellungs-API wird in der Regel verwendet, um einen Codeprofiler zu schreiben – ein Programm, mit dem die Ausführung einer verwalteten Anwendung überwacht wird.

Die Profilerstellungs-API wird von einer Profiler-DLL verwendet, die in den gleichen Prozess geladen wird wie die Anwendung, für die ein Profil erstellt wird. Die Profiler-DLL implementiert eine Rückrufschnittstelle (ICorProfilerCallback in den .NET Framework-Versionen 1.0 und 1.1 und ICorProfilerCallback2 in Version 2.0 und höher). Die CLR ruft die Methoden in dieser Schnittstelle auf, um den Profiler über Ereignisse im profilierten Prozess zu benachrichtigen. Der Profiler kann mithilfe der Methoden in den Schnittstellen ICorProfilerInfo und ICorProfilerInfo2 einen Rückruf in die Runtime ausführen, um Informationen über den Zustand der Anwendung abzurufen, für die ein Profil erstellt wird.

Hinweis

Lediglich der zur Datenerfassung verwendete Teil der Profilerlösung sollte im gleichen Prozess ausgeführt werden wie die Anwendung, für die ein Profil erstellt wird. Alle Benutzeroberflächen- und Datenanalysevorgänge sollten in einem separaten Prozess ausgeführt werden.

Die folgende Abbildung zeigt, wie die Profiler-DLL mit der Anwendung, für die ein Profil erstellt wird, und der CLR interagiert.

Screenshot: Architektur der Profilerstellung

Die Benachrichtigungsschnittstellen

ICorProfilerCallback und ICorProfilerCallback2 können als Benachrichtigungsschnittstellen betrachtet werden. Diese Schnittstellen bestehen aus Methoden wie ClassLoadStarted, ClassLoadFinished und JITCompilationStarted. Jedes Mal, wenn die CLR eine Klasse lädt oder entlädt, eine Funktion kompiliert usw., ruft sie die entsprechende Methode in der ICorProfilerCallback-Schnittstelle oder in der ICorProfilerCallback2-Schnittstelle des Profilers auf.

Beispielsweise könnte ein Profiler über zwei Benachrichtigungsfunktionen die Codeleistung messen: FunctionEnter2 und FunctionLeave2. Dazu versieht er lediglich jede Benachrichtigung mit einem Zeitstempel, sammelt Ergebnisse und gibt eine Liste aus, aus der ersichtlich ist, welche Funktionen während der Anwendungsausführung die meiste CPU- oder Realzeit in Anspruch genommen haben.

Die Datenabrufschnittstellen

Die anderen bei der Profilerstellung vorwiegend verwendeten Schnittstellen sind ICorProfilerInfo und ICorProfilerInfo2. Der Profiler ruft diese Schnittstellen nach Bedarf auf, um weitere Daten für seine Analysen abzurufen. Wenn die CLR beispielsweise die FunctionEnter2-Funktion aufruft, stellt sie einen Funktionsbezeichner bereit. Der Profiler kann weitere Daten über diese Funktion abrufen, indem er die ICorProfilerInfo2::GetFunctionInfo2-Methode aufruft, um die übergeordnete Klasse der Funktion, ihren Namen usw. zu erhalten.

Unterstützte Funktionen

Die Profilerstellungs-API bietet Informationen über verschiedene Ereignisse und Aktionen in der Common Language Runtime. Sie können diese Informationen zum Überwachen der internen Funktionsweise von Prozessen und zum Analysieren der Leistung Ihrer .NET Framework-Anwendung verwenden.

Die Profilerstellungs-API ruft Informationen über die folgenden Aktionen und die Ereignisse ab, die in der CLR auftreten:

  • CLR-Ereignisse beim Starten und Herunterfahren.

  • Ereignisse bei der Anwendungsdomänenerstellung und beim Herunterfahren.

  • Ereignisse beim Laden und Entladen von Assemblys.

  • Ereignisse beim Laden und Entladen von Ereignissen.

  • Ereignisse beim Erstellen und Löschen von COM-vtable.

  • Ereignisse bei der JIT-Kompilierung (Just-In-Time) und beim Codepitching.

  • Ereignisse beim Laden und Entladen von Klassen.

  • Ereignisse beim Erstellen und Löschen von Threads.

  • Ereignisse beim Funktionseinstieg und Funktionsende.

  • Ausnahmen.

  • Übergänge zwischen verwalteter und nicht verwalteter Codeausführung.

  • Übergänge zwischen verschiedenen Laufzeitkontexten.

  • Informationen über Laufzeitunterbrechungen.

  • Informationen über den Laufzeitspeicherheap und die Garbage Collection-Aktivität.

Die Profilerstellungs-API kann von jeder (nicht verwalteten) COM-kompatiblen Sprache aufgerufen werden.

Die API ist im Hinblick auf die Prozessor- und Speicherauslastung effizient. Durch die Profilerstellung werden an der Anwendung mit Profil keine Änderungen durchgeführt, die so signifikant sind, dass es zu falschen Ergebnissen kommt.

Die Profilerstellungs-API ist sowohl für Samplingprofiler als auch für andere Profiler nützlich. Ein Samplingprofiler überprüft das Profil in regelmäßigen Zeiteinheiten, z. B. alle fünf Millisekunden. Ein Nicht-Samplingprofiler wird synchron mit dem Thread, der das Ereignis verursacht, über ein Ereignis informiert.

Nicht unterstützte Funktionalität

Die Profilerstellungs-API unterstützt die folgenden Funktionen nicht:

  • Nicht verwalteter Code, der mit konventionellen Win32-Methoden profiliert werden muss. Der CLR-Profiler umfasst jedoch Übergangsereignisse, um die Grenzen zwischen verwaltetem und nicht verwaltetem Code zu ermitteln.

  • Sich selbst ändernde Anwendungen, die ihren eigenen Code zu bestimmten Zwecken, beispielsweise bei der aspektorientierten Programmierung, selbsttätig ändern.

  • Grenzüberprüfung, da die Profilerstellungs-API diese Informationen nicht bereitstellt. Die CLR bietet systeminterne Unterstützung für die Überprüfung von Grenzen des gesamten verwalteten Codes.

  • Remoteprofilerstellung, die aus den folgenden Gründen nicht unterstützt wird:

    • Durch die Remoteprofilerstellung verlängert sich die Ausführungszeit. Bei der Verwendung der Profilerstellungsschnittstellen müssen Sie die Ausführungszeit minimieren, damit die Auswirkungen auf die Profilerstellungsergebnisse möglichst gering bleiben. Dies trifft insbesondere dann zu, wenn die Ausführungsleistung überwacht wird. Dabei stellt die Remoteprofilerstellung jedoch keine Einschränkung dar, wenn Profilerstellungsschnittstellen zum Überwachen der Speicherauslastung oder zum Abrufen von Laufzeitinformationen über Stapelrahmen, Objekte usw. verwendet werden.

    • Der CLR-Codeprofiler muss mindestens eine Rückrufschnittstelle bei der Laufzeit des lokalen Computers registrieren, auf dem die Anwendung mit Profil ausgeführt wird. Hierdurch wird die Möglichkeit eingeschränkt, einen Remotecodeprofiler zu erstellen.

Benachrichtigungsthreads

In den meisten Fällen führt der Thread, der ein Ereignis generiert, auch Benachrichtigungen aus. Solche Benachrichtigungen (z. B. FunctionEnter und FunctionLeave) müssen die explizite ThreadID nicht angeben. Zudem kann der Profiler basierend auf der ThreadID des jeweiligen Threads entscheiden, seine Analyseblöcke im lokalen Threadspeicher zu speichern und zu aktualisieren, anstatt die Analyseblöcke im globalen Speicher zu indizieren.

Beachten Sie, dass diese Rückrufe nicht serialisiert werden. Benutzer müssen ihren Code schützen, indem sie threadsichere Datenstrukturen erstellen und den Profilercode ggf. sperren, um zu verhindern, dass mehrere Threads parallel darauf zugreifen. Deshalb kann es in bestimmten Fällen passieren, dass Sie eine ungewöhnliche Sequenz von Rückrufen erhalten. Nehmen Sie z. B. an, dass eine verwaltete Anwendung zwei Threads erzeugt, die identischen Code ausführen. In diesem Fall ist es möglich, dass ein ICorProfilerCallback::JITCompilationStarted-Ereignis für eine Funktion aus einem Thread und einen FunctionEnter-Rückruf aus dem anderen Thread empfangen wird, bevor der ICorProfilerCallback::JITCompilationFinished-Rückruf eingeht. In diesem Fall erhält der Benutzer einen FunctionEnter-Rückruf für eine Funktion, für die möglicherweise noch keine vollständige JIT-Kompilierung (Just-In-Time) erfolgt ist.

Sicherheit

Eine Profilerstellungs-DLL ist eine nicht verwaltete DLL, die als Teil der Common Language Runtime-Ausführungs-Engine ausgeführt wird. Daher gelten die Einschränkungen für die Sicherheit des Zugriffs auf verwalteten Code nicht für den Code in der Profilerstellungs-DLL. Für die Profilerstellungs-DLL gelten nur die Einschränkungen, die das Betriebssystem für den Benutzer erzwingt, der die Anwendung mit Profil ausführt.

Entwickler von Profilern sollten entsprechende Vorkehrungen treffen, um sicherheitsrelevante Probleme zu vermeiden. So sollte beispielsweise eine Profilerstellungs-DLL während der Installation in eine Zugriffssteuerungsliste (ACL) aufgenommen werden, damit sie nicht durch böswillige Benutzer geändert werden kann.

Kombination von verwaltetem und nicht verwaltetem Code in einem Codeprofiler

Ein falsch geschriebener Profiler kann zirkuläre Verweise auf sich selbst verursachen und zu unvorhersehbarem Verhalten führen.

Bei näherer Betrachtung der Profilerstellungs-API kann der Eindruck entstehen, dass es möglich wäre, einen Profiler mit verwalteten und nicht verwalteten Komponenten zu schreiben, die sich gegenseitig über COM-Interop oder indirekte Aufrufe aufrufen.

Obwohl dies aus der Entwurfsperspektive möglich ist, unterstützt die Profilerstellungs-API keine verwalteten Komponenten. Ein CLR-Profiler darf keinerlei verwaltete Komponenten enthalten. Versuche, verwalteten und nicht verwalteten Code in einem CLR-Profiler zu kombinieren, können Regelverletzungen, Programmausfälle oder Deadlocks verursachen. Die verwalteten Komponenten des Profilers verweisen Ereignisse zurück an ihre nicht verwalteten Komponenten, die in der Folge erneut die verwalteten Komponenten aufrufen. Dies führt zu zirkulären Verweisen.

Der einzige Ort, an dem ein CLR-Profiler verwalteten Code sicher aufrufen kann, befindet sich im CIL-Textkörper (Common Intermediate Language, CIL) einer Methode. Die empfohlene Methode zum Ändern des CIL-Texts besteht darin, die JIT-Rekompilierungsmethoden in der ICorProfilerCallback4-Schnittstelle zu verwenden.

Es ist auch möglich, die älteren Instrumentierungsmethoden zum Ändern von CIL zu verwenden. Bevor die Just-in-Time-Kompilierung einer Funktion abgeschlossen ist, kann der Profiler verwaltete Aufrufe im CIL-Textkörper einer Methode einfügen und dann JIT-kompilieren (siehe ICorProfilerInfo ::GetILFunctionBody-Methode ). Diese Technik lässt sich erfolgreich für die selektive Instrumentierung von verwaltetem Code oder für die Erfassung von Statistik- und Leistungsdaten für JIT verwenden.

Alternativ kann ein Codeprofiler systemeigene Hooks im CIL-Textkörper jeder verwalteten Funktion einfügen, die in nicht verwalteten Code aufruft. Diese Technik kann für Instrumentation und Abdeckung verwendet werden. Beispielsweise könnte ein Codeprofiler Instrumentierungshaken nach jedem CIL-Block einfügen, um sicherzustellen, dass der Block ausgeführt wurde. Die Änderung des CIL-Körpers einer Methode ist eine sehr heikle Operation, und es gibt viele Faktoren, die berücksichtigt werden sollten.

Profilerstellung für nicht verwalteten Code

Die Profilerstellungs-API der Common Language Runtime (CLR) bietet minimale Unterstützung zur Profilerstellung für nicht verwalteten Code. Die folgende Funktionalität wird bereitgestellt:

  • Enumeration von Stapelketten. Diese Funktion ermöglicht einem Codeprofiler, die Grenze zwischen verwaltetem und nicht verwaltetem Code zu bestimmen.

  • Feststellung, ob eine Stapelkette verwaltetem oder nativem Code entspricht.

In .NET Framework, Version 1.0 und 1.1, sind diese Methoden über den prozessinternen Teil der Debug-API der CLR verfügbar. Sie sind in der Datei "CorDebug.idl" definiert.

In .NET Framework 2.0 und höher können Sie die ICorProfilerInfo2::DoStackSnapshot-Methode für diese Funktionalität verwenden.

Verwenden von COM

Obwohl die Profilerstellungsschnittstellen als COM-Schnittstellen definiert sind, initialisiert die Common Language Runtime (CLR) COM tatsächlich nicht zur Verwendung dieser Schnittstellen. Es soll vermieden werden, das Threadingmodell mithilfe der Funktion CoInitialize festlegen zu müssen, bevor die verwaltete Anwendung die Möglichkeit hatte, das gewünschte Threadingmodell festzulegen. Ähnlich sollte auch der Profiler selbst CoInitialize, nicht aufrufen, da sonst ein Threadingmodell ausgewählt werden könnte, das mit der Anwendung, für die ein Profil erstellt wird, nicht kompatibel ist und es daher zu einem Anwendungsfehler kommen kann.

Aufruflisten

Die Profilerstellungs-API bietet zwei Methoden zum Abrufen von Aufruflisten: eine Stapelmomentaufnahmemethode, mit der Anruflisten sporadisch überwacht werden können, und eine Schattenstapelmethode, mit der Anruflisten laufend überwacht werden können.

Stapelmomentaufnahme

Unter einer Stapelmomentaufnahme versteht man die Überwachung eines Threadstapels zu einem bestimmten Zeitpunkt. Die Profilerstellungs-API unterstützt die Überwachung von verwalteten Funktionen im Stapel, überlässt jedoch die Überwachung nicht verwalteter Funktionen dem Stackwalker des Profilers.

Weitere Informationen darüber, wie Sie den Profiler zum Durchlaufen verwalteter Stapel programmieren, finden Sie bei der ICorProfilerInfo2::DoStackSnapshot-Methode in diesem Dokumentationssatz und unter Profiler Stack Walking in the .NET Framework 2.0: Basics and Beyond.

Schattenstapel

Die allzu häufige Verwendung der Momentaufnahmemethode kann schnell zu Leistungseinbußen führen. Wenn Sie regelmäßig Stapelüberwachungen vornehmen möchten, sollte der Profiler stattdessen die AusnahmerückrufeFunctionEnter2, FunctionLeave2, FunctionTailcall2 und ICorProfilerCallback2 verwenden, um einen Schattenstapel zu erstellen. Der Schattenstapel ist immer aktuell und kann schnell in den Speicher kopiert werden, wenn eine Stapelmomentaufnahme benötigt wird.

Mit einem Schattenstapel können Funktionsargumente, Rückgabewerte und Informationen über generische Instanziierungen abgerufen werden. Diese Informationen sind nur über den Schattenstapel verfügbar und können abgerufen werden, wenn die Steuerung an eine Funktion übergeben wird. Sobald die Funktion ausgeführt wird, sind diese Informationen u. U. jedoch nicht mehr verfügbar.

Rückrufe und Stapeltiefe

Profilerrückrufe können ausgegeben werden, wenn der Stapel stark eingeschränkt ist, und ein Stapelüberlauf in einem Profilerrückruf führt zum sofortigen Prozessende. Ein Profiler sollte bei der Reaktion auf Rückrufe möglichst wenige Stapel verwenden. Wenn der Profiler für Prozesse verwendet werden soll, die vor Stapelüberlauf geschützt sind, sollte der Profiler selbst möglichst auch keinen Stapelüberlauf auslösen.

Titel BESCHREIBUNG
Einrichten einer Profilerstellungsumgebung Erklärt, wie ein Profiler initialisiert, Ereignisbenachrichtigungen festgelegt und ein Profil für einen Windows-Dienst erstellt werden.
Profilerstellungsschnittstellen Beschreibt die nicht verwalteten Schnittstellen, die die Profilerstellungs-API verwendet.
Profilerstellung für globale statische Funktionen Beschreibt die nicht verwalteten globalen statischen Funktionen, die die Profilerstellungs-API verwendet.
Profilerstellungsenumerationen Beschreibt die nicht verwalteten Enumerationen, die die Profilerstellungs-API verwendet.
Profilerstellungsstrukturen Beschreibt die nicht verwalteten Strukturen, die die Profilerstellungs-API verwendet.