Performance von Schnittstellen in .NET – aka The Good, The Bad and The Ugly :-)

Reduziert auf ein ganz triviales Beispiel zeigt sich ein erhebliches Problem in der Art, wie .NET den Zugriff über Schnittstellen in nativen Code umsetzt – zumindest im normalen JIT Modus und in .NET 2.0 / 4.0. In einer Bibliothek befindet sich ein ganz harmlos ausschauender Code:

public static class Summer
{
public static long Sum( IEnumerable summer )
{
if (summer == null)
return 0;

long res = 0;

using (var e = summer.GetEnumerator())
while (e.MoveNext())
res += e.Current;

return res;
}
}

Dieser Code wird von einem Testprogramm mit verschiedenen Parametern aufgerufen:

const int n = 10000000;

var all = Enumerable.Range( 0, n ).Select( i => (long) i );

var asArray = all.ToArray();
var asList = all.ToList();
var asHash = new HashSet( all );

Dass die ermittelten Laufzeiten unterschiedlich sind (104 Millisekunden für das Feld, 123 für die Liste und 155 für den HashSet auf meinem x86 Vista) ist in Ordnung und ohne Relevanz. Was aber erst einmal extrem überrascht ist folgendes: diese Zeiten ergeben sich, wenn man im Testprogramm immer genau eine der Implementierungen verwendet. Werden alle drei hintereinander in die Methode der Bibliothek geschickt (etwa erst Feld, dann Liste, dann HashSet), so zeigt sich ein erheblicher Unterschied.

Wird das Feld als erstes verwendet, so ist die Laufzeit weiterhin 104ms. Wird allerdings eine beliebige andere Liste vorher aufgerufen, so landen wir plötzlich bei 170ms – das ist 63% mehr! Bei Liste und HashSet sind es immerhin noch 33% (123ms auf 175ms respektive 155ms auf 206ms). Wie kann so etwas sein? Es wird doch immer der selbe Code ausgeführt?

Die Antwort findet sich in diesem Quellcode von Microsoft. Im Endeffekt läuft es auf folgendes hinaus: wenn eine Methode einer Schnittstelle aufgerufen wird, so muss .NET herausfinden, welche Klassenmethode tatsächlich dazu gehört – dazu im Anhang ein kleiner Exkurs in die Legacy Welt von COM und MFC. Der JITter erzeugt nun für jede Methode jeder Schnittstelle ein spezielles Stück Code. Dieser Code ermittelt die Objektinstanz zur Klasse und darüber ein weiteres dynamisch erzeugtes Codefragment, das zur korrekten Methode in der Klasse verzweigt. Das Nachschlagen von Klasse zu letzterem Codefragment geschieht über eine Hashtable (mit 4096 Einträgen). Ist beim ersten Aufruf ein solcher Eintrag nicht vorhanden, so wird er automatisch erzeugt.

Da diese Nachschlageoperation selbst bei vorhandenem Eintrag in der Hashtable teuer ist, verwendet .NET einen kleinen Trick: der erste erzeugte Eintrag wird statisch geprüft. Ein direkter Vergleich auf die Klasse führt direkt zur Umlenkung. Daher der beobachtete Effekt, dass jeweils der erste Aufruf in Normalzeit abläuft und lediglich die Schnittstellen auf andere Klassen verzögert werden.

Leider ist das noch nicht die ganze Wahrheit, denn .NET verwendet eine Optimierung: wenn bei einem Aufruf nicht diese erste Schnittstelle verwendet wird, so wird ein Zähler verändert. Nach etwa 100 Fehlversuchen wird dann die Prüfung auf die erste Schnittstelle umgangen und direkt auf die Nachschlageoperation der Hashtabelle verwiesen. Würde man im Beispiel also etwa die Sequenz Feld, Liste, HashSet, Feld verwenden, so wäre bei der zweiten Nutzung des Feldes auch hier wieder die Verlangsamung zu beobachten. Wohlgemerkt: zwei identische Aufrufe unterscheiden sich in der Laufzeit um 63% – reproduzierbar!

Richtig kritisch würde es, wenn eine ständige Hash Kollision entstehen würde, da dann der Eintrag der Hashtabelle ständig neu ermittelt werden müßte. Bei 4096 Einträgen nicht wirklich wahrscheinlich, oder? Nun, der Effekt tritt natürlich nur in Szenarien auf, in denen die neben dem Aufruf der Schnittstellenmethoden (hier MoveNext und Current) sehr wenig anderes gemacht wird. In diesem Sinne ist das Beispiel natürlich gemein – aber sicher nicht exotisch! Ich würde sogar soweit gehen, dieses Feature als einen LINQ Killer zu bezeichnen. Gerade bei Verwendung von LINQ werden Schnittstellenmethoden in extremster Weise wiederverwendet und LINQ Operationen dadurch langsamer als nötig – vermutlich im Normalfall unmerklich. Ich habe mir jetzt nicht die Mühe gemacht alle Kommentare von Microsoft im obigen Source Code zu prüfen, aber es kann sein, dass diese Verwaltung mindestens Thread-übergreifen evtl. aber sogar für alle AppDomains in einem Prozess gemeinsam erfolgt. Hier könnte es zu unerwünschten Seiteneffekten durch völlig anderen Code kommen – etwa mehrere virtuelle Verzeichnisse in einem IIS Application Pool.

Soweit dazu

Jochen

Nun der versprochene Exkurs COM / MFC: programmiert man COM Objekte in C++ mit MFC / ATL, so können diese mehrere Schnittstellen unterstützen. Eine COM Schnittstelle wird dabei im Allgemeinen (Ausnahmen bestätigen die Regeln) als innere Klassen implementiert. Ein Aufruf auf eine Schnittstellenmethode erfolgt de facto mit einem für diese innere Klasse und damit Schnittstelle individuellen this Zeiger. Alle Schnittstellenmethoden wissen dies aber und korrigieren den Zeiger entsprechend – gemäß dem Offset der inneren Klasse in dem COM Objekt. Mit diesem veränderten this Zeiger kann dann transparent ein Aufruf einer Methode der eigentlichen Klasse erfolgen. Der Unterschied zu .NET ist allerdings, dass nun ein Zeiger auf eine Schnittstelle nicht mehr einfach mit einem Zeiger auf eine andere Klasse oder das COM Objekt verglichen werden kann (== aka ReferenceEquals) um festzustellen, dass tatsächlich mit einer identischen Objektinstanz gearbeitet wird. COM hat dazu eine einfache, aber nicht ganz billige Regel: zum Vergleich muss zwingend ein QueryInterface auf die IUnknown Schnittstelle herangezogen werden.

.NET hat das Problem anders gelöst und wohl die Prämisse gesetzt, dass der this Zeiger bei jedem Aufruf jeder Schnittstellenmethode identisch zur Objektreferenz sein muss. Die COM / MFC / ATL Codefragmente, die bei C++ der Compiler erzeugt, muss nun der JITter anlegen – mit den beschriebenen Nachteilen. Schön ist, dass man nun kein QueryInterface für ReferenceEquals / == braucht. Ich halte diesen Ansatz aber für extrem bedenklich und würde (wäre ich bei Microsoft für diesen .NET Teil zuständig, was ich glücklicherweise nicht bin) über einen BSTR artigen Ansatz nachdenken. Die Idee läßt sich wie folgt beschreiben: jede Schnittstelle zeigt auf eine eigene kleine Implementierung, bei der eine COM / MFC / ATL ähnliche Umsetzung erfolgt. Diese Umsetzung kann einmalig vom JITter für jede Schnittstelle jeder Klasse erstellt werden – wir sind dann weg von den Methodenaufrufen! Zu jeder Objektinstanz im Speicher wird vor den heutigen Objektdaten ein Zeiger auf die primäre Instanz (der Klasse) festgehalten – bei Verwendung der primären Klassenschnittstelle wäre dies dann genau der this Zeiger selbst. Ein ReferenceEquals müßte dann allerdings für beide Objekte eine zusätzlichen Speicherzugriff ausführen, bevor ein Vergleich stattfinden kann. Und jede Objektinstanz würde 4 / 8 Bytes größer. Schaue ich mir aber den heutigen Effekt und die Forcierung von LINQ an, scheint es mir durchaus wert, diese Richtung nicht von vorneherein auszuschliessen – mal unabhängig davon, dass ich natürlich nur die Spitze des Eisbergs sehe und es viele .NET Interna geben kann, die eine solche Implementierung grundsätzlich ausschließen.

Spielen mit Logikgattern

Bei meiner weiteren Einarbeitung in Silverlight 4 habe ich ein kleines Spielzeug gebastelt, mit dem man sich die Funktionalitäten von Logikgattern anschauen kann. Etwas wirklich Sinnvolles macht das Progrämmchen nicht und schon bei getackteten FlipFlops würde es gnadenlos scheitern. Aber es simuliert zumindest einfache statische Schaltungen – wenn man auf allzuviele Tricks verzichtet.

So sieht das dann aus

Man kann es sich natürlich auch Online abschauen (na gut: eine Bedienungsanleitung gibt es nicht und ohne wird es schwer) und den Quellcode gibt es selbstverständlich auch. Meine Lerneffekte waren so in etwa:

* Darstellen der Bausteine auf Basis eines einzigen Controls über verschiedene Styles.
* Arbeiten mit ContentTemplates und dynamischen Elementen darin.
* Lokalisieren von Elementen über TransformToVisual / TransformBounds.
* Einsatz der Viewbox zur sauberen Skalierung von freien Zeichnungen (der Gattern) in die gewünschten Bereiche.
* Drucken und dabei Skalieren mit der Viewbox sowie das Kennenlernen einiger UpdateLayout Fallstricke.
* Speichern in und Laden aus Dateien im lokalen Dateisystem und mehr UpdateLayout Fallstricke.
* Speichern in und Laden aus dem IsolatedStorage sowie Zugriff auf diesen inkl. Löschen – ok, das ist nun wirklich nichts Wildes.
* Einsatz eines Canvas als Overlay zur Annotationen – hier die Verbindungen von Aus- und Eingängen.
* Ein bißchen über Styles und was sie nicht können.

Viel Spaß

Jochen

Silverlight 4 unter XP: kleines Ups bei String.IndexOf

In einer Lösung werden auf eine Benutzereingabe hin ca. 10.000 (eher weniger) Zeichenketten (einige kurz, einige länger, aber im Mittel sicher nicht mehr als 200 Zeichen) darauf untersucht, ob sie eine andere Zeichenkette (im Beispiel 7 Zeichen) enthalten. Dazu wird String.IndexOf verwendet. Unter Vista geht das so schnell, dass man praktisch keine Verzögerung zwischen Beginn der Suche und dem Ergebnis sieht – daher hatte ich für den einfachen Test auch nicht nach eleganteren Möglichkeiten der Suche Ausschau gehalten. Bei gleicher Hardware unter XP dauert die Operation insgesamt aber etwa 30 (!!!) Sekunden – ich schätze mal, mindestens ein Faktor 100 langsamer.

Um auszuschliessen, dass es an den Daten liegt, habe ich das IndexOf naiv über ToCharArray() und einen wirklich primitiven Suchlauf gemacht – einfacher geht es nicht: erstes Zeichen des Suchbegriffs suchen und schauen, ob die Folgezeichen stimmen und wenn nicht ein Zeichen weiter neu suchen etc. Schlechter kann die Silverlight Implementierung eigentlich nicht sein – ist sie aber: hier gibt es nun auch unter XP keine Verzögerung mehr!

Dies als kleine Warnung: selbst auf Microsoft Plattformen scheint Silverlight nicht so furchtbar plattformunabhängig zu sein und hier geht es um eine primitive Laufzeitmethode!

Jochen

PS: Habe ich wohl nicht ausreichend respektive falsch recherchiert, ist ja schon länger bekannt. Sorry!

VCR.NET goes Silverlight?

Nein, nicht wirklich. Aber da ich mich in Silverlight 4 einarbeiten wollte, um einige Konzepte zu verstehen, habe ich mir als lebendes Beispiel VCR.NET ausgesucht. Nutzbar ist das Ergebnis wohl nur in einem sehr engen Rahmen, da es wirklich nur eine technische Studie vor dem Hintergrund eines knappen Zeitbudgets ist. Aber wer mal reinschauen möchte, kann sich die beiden Dateien von hier in sein VCR.NET Installationsverzeichnis kopieren, der Aufruf erfolgt mittels http://localhost/VCR.NET/Silverlight.html – Silverlight 4 muss natürlich lokal installiert sein. Die Quellen sind wie gewohnt öffentlich verfügbar. Die Nutzung erfolgt ohne Gewähr, ich hatte keine Zeit, alle technischen VCR.NET Aspekte bis auf’s Letzte zu prüfen – ich werde den Client jetzt mal für das nutzen, was er kann und evtl. einen Update nachschieben. Die Studie versteht sich als solche und wird nicht Teil der nächsten VCR.NET Version sein.

Hier einfach so ein paar Aspekte des Clients in bunt gewürfelter Reihenfolge – Silverlight, VCR.NET, Technik, …

  • * Ein definiertes Ziel war es, im Oberflächenbereich soviel wie möglich auf XAML zu setzen und so wenig wie möglich Code zu schreiben. Das ist nur zum Teil gelungen, da vor allem die Programmzeitschrift doch einiges an Logik erfordert.
  • * Als nützlich hat sich die gemischte Nutzung von vordefinierten (App.xaml) und programmatischen (App.Resources) Ressourcen erwiesen. Es scheint allerdings etwas kniffeliger zu sein, letztere in der Startphase zur Nutzung in XAML zur Verfügung zu stellen
  • * Bei den Einstellungen habe ich ein wenig mit den Formularmöglichkeiten herumgespielt, etwa mit der Validierungsinfrastruktur oder dem praktischen Label Control.
  • * Grundsätzlich sollte alles für eine einfache Übersetzung in andere Sprachen (Lokalisierung) vorbereitet sein.
  • * Auf Deployment Techniken wie Bibliotheken bin ich bewußt nicht eingegangen.
  • * Einer der zwei wichtigen Aspekte der Studie ist die Programmzeitschrift mit ihren Filtermöglichkeiten. Hier gab es einige Ärgernisse von Silverlight vs. WPF zu ignorieren (in einer ComboBox kann man ohne weiteren Code nicht über Tasteneingaben auswählen) oder zu umgehen (die Gruppierung der Ergebnisliste nach Tag und Stunde – Gruppierungen kennt in Silverlight erst einmal nur das DataGrid). Ganz nett war es zu sehen, wie einfach man aus einem ItemsControl eine Art ListBox machen kann, die ein Auswahlfeedback bietet.
  • * Die Liste der geplanten Aufzeichnungen ist eher langweilig, hier habe ich ein bißchen mit dem DataGrid experimentiert. Ganz interessant ist die Detailanzeige von Mehrkanalaufzeichnungen, die sich auf die Ausawhl einer Aufzeichnung öffnet. Hier sind man genau, welche anderen Aufzeichnungen verhindert oder parallel aufgezeichnet werden.
  • * Meine erste Aktion war der Navigationsknopf zum Blättern, der optisch ein wenig (nicht mehr) hermachen sollte. Er blendet sich auch fast vollständig aus, wenn die Maus nicht gerade auf ihm steht.
  • * Die Animation beim Blättern ist ein reiner Test – vermutlich in der Praxis eher nervig.
  • * Es werden auch die laufenden Aufzeichnungen angezeigt. Hier war aber die Zeit zu Ende und außer genau der Anzeige kann man nicht viel machen.
  • * Im XAML wird an vielen Stellen mit Konvertern (IValueConverter) gearbeitet, vor allem zur Anzeige von Uhrzeiten (VCR.NET verwendet grundsätzlich GMT / UTC und nicht die lokale Zeitzone) oder der Auswahl von Symbolen. Richtig eingesetzt sind Konverter trotz einfachster Codierung offenbar eine starke Waffe zur Unterstützung der Dynamik – man siehe auch diesen Artikel.
  • * Die Trennung zwischen Layout und Inhalt ist sicher noch unzureichend. Zwar habe ich viel auf Styles gesetzt, aber enthalten diese dann doch zum Teil wieder Bezüge zu ihrer Umgebung – unschön, das geht sicher auch professioneller.
  • * Beim Anbindung des SOAP Web Services vom VCR.NET hat es sich als nützlich erwiesen, dass alle generierten Klassen partial sind. So konnte etwa das EPGEvent so erweitert werden, dass neu hinzugefügte Eigenschaften direkt in XAML Bindings referenzierbar sind.

Ok, das war sicher nicht alles, muss aber für jetzt reichen!

Viel Spaß

Jochen

Silverlight ListBox mit kontextabhängigem ItemTemplate

In einer ListBox von Silverlight 4 sollen .NET Objekte dargestellt werden, die zwar einer gemeinsamen Basisklasse angehören, individuell aber unterschiedliche Ausprägungen haben – im Beispiel habe ich mal eine abstrakte Basisklasse Animal (Tier) genommen und davon Insect (Insekten), Mammal (Säugetiere) und Bird (Vögel) abgeleitet. Im realen Leben soll die ListBox für die unterschiedlichen konkreten Klassen völlig unterschiedliche Darstellungen verwenden. Im Beispiel habe ich hier einfach mal einen Präfix und eine Farbunterlegung verwendet.

Beispiel

Links sieht man die normale Darstellung und rechts so, wie ich mir das vorstelle. Den gesamten Quellcode für das Beispiel habe ich hier bereitgestellt, hier nur kurz die wesentlichen Tricks (wenn es einfacher geht, wäre ich durchaus an einem Hinweis interessiert).

Die rechte ListBox verwendet ein ItemTemplate / DataTemplate, in dem eine dynamische Bindung des Styles an ein ContentControl vorgenommen wird.

<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Style="{Binding Converter={StaticResource style}}" />
</DataTemplate>
</ListBox.ItemTemplate>

Bei der statischen Ressource handelt es sich um einen IValueConverter, der nach einer Namenskonvention beliebigen Instanzen von .NET Objekten eine statische XAML Ressource zuordnet.

<app:StyleChooser x:Key="style" />

object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
if (value == null)
return null;
else if (targetType == typeof( Style ))
return App.Current.Resources[value.GetType().Name + "ClassStyle"];
else
return null;
}

Nun muss nur noch für jede Klasse ein Style in der verwendete Namenskonvention bereitgestellt werden. Da hier ein ContentControl verwendet wird, hat man im ControlTemplate alle Möglichkeiten, die auch ein DataTemplate bietet.

<Style x:Key="MammalClassStyle" TargetType="ContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Background="Gray" Orientation="Horizontal">
<TextBlock Text="Das ist ein Säugetier: " />
<TextBlock Text="{Binding}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Happy Coding

Jochen