.NET Framework vorab laden?

Situation

Ein .NET Programm startet langsam. Ich bin mit der Zeit nicht zufrieden. Als Ursache vermute ich das .NET Framework selbst. Das Laden des .NET Frameworks mit all seinen DLLs dauert einige Zeit.

Lösungsansatz

Beim Start des Rechners könnte ein .NET Programm geladen werden, beispielsweise als Programm im Autostart-Ordner. Dadurch befinden sich die benötigten DLLs bereits im Speicher und die folgenden Anwendungen können auf die bereits geladenen DLLs zugreifen. Außerdem basieren vermutlich immer mehr Anwendungen auf dem .NET Framework, so dass mehrere Programme davon profitieren.

Lösungsbewertung

Doch was bedeutet das für den PC? Zunächst einmal verzögert sich der Startvorgang des PCs. Im Gegenzug verbessert sich der Startvorgang des betroffenen Programms.
Weiterhin ist fraglich, welche DLLs geladen werden sollen. Ein maßgeschneidertes Programm hat den Vorteil, dass nur die DLLs geladen werden, die das betroffene Programm dann tatsächlich auch nutzt. Beim generellen Laden von allen DLLs profitieren sämtliche Programme. Da ich vermute, dass es mehrere .NET Programme gibt, habe ich mich zur weiteren Analyse des generellen Ansatzes entschieden.

Beim generellen Ansatz durchsuche ich das Verzeichnis C:\Windows\assembly\GAC_MSIL (dort befinden sich die .NET 2.0 DLLs) rekursiv und ermittle alle enthaltenen DLLs. Dann versucht das Programm, die DLL als Assembly zu laden. Gelingt dies, wird das Assembly einer Liste hinzugefügt. Gelingt es nicht, dann war die DLL möglicherweise keine .NET DLL oder eine .NET DLL in der falschen Frameworkversion.

Eine Übersicht über den Speicher verschaffe ich mir mit dem Tool SysInternals VMMap (rev. 2011-03-07). Das erstellte Programm belegt nach dem Laden aller DLLs 530 MB Speicher (Committed). Davon werden jedoch ca. 470 MB gleich wieder ausgelagert, so dass nur ca. 60 MB im RAM (WorkingSet) verbleiben.  DLLs sind in dieser Auflistung unter „Image“ angegeben (siehe Grafik).

Das bedeutet: es wurden 530 MB von Platte gelesen und 470 MB wieder auf Platte geschrieben. Je nach Geschwindigkeit der Festplatten dauert das beträchtlich lange. Hinzu kommt noch die Zeit, die benötigt wird, um die Assemblies zu initialisieren. Auf meinem Rechner ist das Laden erst nach über 2 Minuten abgeschlossen. Doch welchen Effekt hat dies nun auf das betroffene Programm?

Auch auf diese Frage gibt es eine Antwort von VMMap: die Bytes, die sich die Anwendung mit anderen Anwendungen teilen kann, sind unter „Shareable WS“ aufgelistet. Die Angaben sind plausibel und erwartet: am meisten Speicher können sich Anwendungen über Dateien teilen. In der Grafik werden unmittelbar nach dem Start ca. 51 MB an teilbaren DLLs ausgewiesen.

Doch dieser Wert ist mit Vorsicht zu genießen, denn das WorkingSet verändert sich ständig. Wenn Windows für andere Anwendungen mehr Speicher benötigt, lagert es weiter auf die Festplatte aus. Dadurch verringert sich das WorkingSet und somit auch die Anzahl an teilbaren Dateien. Im bisher ungünstigsten von mir beobachteten Fall sank das WorkingSet auf 2,8 MB, was den Performancegewinn komplett zunichte macht.

Doch all das sind nur theoretische Anhaltspunkte. Gibt es vielleicht doch eine spürbare Verbesserung beim Start des betroffenen Programms? Auch hier sind ein paar Rahmenbedingungen zu beachten: einerseits lässt sich das Verhalten nur richtig beim „Kaltstart“ des Programms beobachten. Beim „Warmstart“ befinden sich ggf. viele Dateien noch im Cache des Betriebssystems. Also sollte der PC zwischen zwei Tests neu gebootet werden.

Damit das Ganze nicht so subjektiv ist, habe ich eine kleine Batchdatei entwickelt, welche die Dauer zum Laden eines .NET Programms misst. Die Batchdatei gibt einfach die Zeit vor und nach dem Start der Anwendung an.

@echo off
echo | time
start /wait simplewinforms.exe
echo | time
pause

Das .NET Programm zeigt ein leeres Fenster an, wartet auf das „Shown“ Event und beendet sich dann sofort wieder.

public Form1()
{
    InitializeComponent();
    Shown += OnShown;
}
private void OnShown(object sender, EventArgs e)
{
    Close();
}

Mit diesen Mitteln ausgestattet habe ich meinen Rechner drei Mal je Konfiguration neu gestartet. Nach dem Anmelden habe ich gewartet, bis die Festplattenlampe nicht mehr leuchtete, 30 Sekunden zusätzlich gewartet und dann die Batch-Datei gestartet. Den ganzen Ablauf habe ich einmal ohne das Ladeprogramm und einmal mit dem Ladeprogramm abgearbeitet.

Im dritten Durchgang habe ich simuliert, was passiert, wenn in der Zwischenzeit viele Programme gestartet werden. Dazu habe ich vor Start des .NET Programms ein C++ Programm gestartet, welches 1,3 GB Speicher anfordert (bei 2,0 GB physikalischem RAM), wieder freigibt und dann auf einen Tastendruck wartet. Die Erwartung ist, dass das WorkingSet des .NET Ladeprogramms stark verringert wird und dadurch die Performance-Auswirkungen geringer sind.

int main(int argc, char* argv[])
{
    void *buffer;
    buffer = malloc(1300000000);
    if (buffer==NULL)
    {
        printf("malloc failed");
        getch();
        return 1;
    };
    free(buffer);
    printf ("Press a key.");
    getch();
    return 0;
}

Hier die Ergebnisse ohne Ladeprogramm:
3,11 Sekunden
3,57 Sekunden
3,62 Sekunden

Und die Ergebnisse mit dem Ladeprogramm:
1,00 Sekunden
0,98 Sekunden
1,03 Sekunden

Bei der Simulation von mehreren Programmstarts (und freigegebenem Speicher):
1,87 Sekunden
1,95 Sekunden
1,82 Sekunden

Bei der Simulation von mehreren Programmstarts (und belegtem Speicher):
– noch keine Messergebnisse –

Wie sieht es eigentlich mit der Vermutung aus, dass immer mehr Programme mit .NET entwickelt werden? Das Tool der Wahl ist hier der SysInternals Process Explorer (rev. 2011-03-07). Über Options/Configure Highlighting kann eingestellt werden, wie Programme eingefärbt werden. Da unklar ist, welche Prioritäten die Farben haben, wählt man am besten nur .NET aus. Diese werden dann gelb markiert. So ausgestattet stelle ich auf meinem PC fest, dass nach dem Booten überhaupt kein .NET Programm aktiv ist. Zumindest auf meinem PC trifft die Vermutung also nicht zu. Ansonsten müsste das betroffene Programm ja auch bereits von den anderen Programmen profitieren, da ein Teil der DLLs schon geladen sein müsste.

Schwachstellen der Analyse

RAM wird immer billiger. Bei einer entsprechenden Ausstattung an RAM könnte die Auslagerungsdatei komplett abgeschaltet werden. Dann werden alle Daten zwangsläufig im RAM gehalten und der Performancegewinn müsste deutlich höher ausfallen. Doch selbst bei 8 GB RAM frage ich mich, ob ich gleich 500 MB davon nur für das Laden von .NET DLLs opfern soll.

Das gestartete Programm verwendet außer seiner eigenen DLL nur DLLs, die tatsächlich auch vom Ladeprogramm vorgeladen werden. Andere Programme laden jedoch typischerweise noch weitere 3rd-Party DLLs, die nicht vom Ladeprogramm erfasst werden. Für diese DLLs ergibt sich kein Performancegewinn.

Die Simulation von Programmstarts (Belegung von 1,3 GB Speicher) gibt den Speicher wieder frei. Dadurch trifft das zur Messung verwendete .NET Programm bei seinem Start auf 1,3 GB frisch freigegebenen Speicher und profitiert dadurch vermutlich, weil es nicht zuerst andere Programme auf Platte auslagern muss. Im realen Betrieb dürfte der Peformancegewinn schlechter ausfallen.

Fazit

Es ist schwierig, die Vor- und Nachteile des Ladens aller .NET Framework DLLs beim Start des PCs zu beurteilen. Wie die Untersuchungen zeigen, hängt ein möglicher Performancegewinn von vielen Faktoren ab, bei denen es schwer fällt, eine objektive Aussage zu treffen. Die gemessenen Zeiten zeigen jedoch, dass durch ein systematisches Vorgehen wenigstens reproduzierbare Ergebnisse ermittelt werden können.

Abhängig vom Benutzerverhalten (früher oder später Start des betroffenen Programms), vom verfügbaren Speicher (RAM) und von der speziellen Optimierung des Ladeprogramms auf das Problem lassen sich unterschiedliche Performancegewinne beobachten, die jedoch in jedem Fall negativen Einfluss auf die Bootzeit des Rechners haben.

Falls Sie Untersuchungen auf Ihrem eigenen System durchführen möchten, stelle ich in den nächsten Tagen die Programme im Quellcode und als ausführbare Programme bereit. Die Untersuchungen sowie Programme sind für 32 Bit Betriebssysteme ausgelegt, sollten aber auch in einer 64 Bit Umgebung im WoW64-Modus laufen.