Archiv der Kategorie: Code

Warum dein Entity Framework langsam ist Teil 3

In Teil 1 haben wir uns den generellen Aufbau angeschaut und mit IQueryable, IEnumerable und ToList die Basis gelegt.
In Teil 2 haben wir anschließend erste Performanceoptimierung mit Include und einzelnen Abfragen durchgeführt.
In diesem Teil werden wir nun die Entity Framework Abfragen noch parallelisieren.
Weiterlesen

Warum dein Entity Framework langsam ist Teil 2

In Teil 1 haben wir uns den generellen Aufbau angeschaut und mit IQueryable, IEnumerable und ToList die Basis gelegt. Nun wollen wir uns anschauen, wie wir die Abfrage tatsächlich schneller bekommen.

Weniger Abfragen: Include

Mit ToList haben wir bereits die Customer Tabelle in den Speicher bekommen. Unsere verschachtelten foreach-Schleifen sorgen nun allerdings für Lazy Loading, d.h. es wird mit jeder Verschachtelungstiefe erneut eine Anfrage an die Datenbank gesendet. Es werden also in eienr Abfrage alle Customer Objekte geladen. In der nächsten wird zu einem Customer alle SalesOrderHeader Objekte geladen. Zum Schluss werden dann noch zu jedem SalesOrderHeader alle SalesOrderDetail Objekte geladen. Daraus resultieren die bisherigen 880 SQL SELECT Statements. Mit Include können wir diese Referenzen in einer Abfrage laden.

var customers = context.Customer
    .Include(c => c.SalesOrderHeader.Select(h => h.SalesOrderDetail.Select(d => d.Product)))
    .ToList();

Sum is 353.504,37
Used 1 SQL SELECT Statements
Get took 3275ms
Output took 1ms

Wir haben nun also mit einer Abfrage alle Tabellen auf einmal geladen. Dies kann nützlich sein (besonders für UPDATE Statements), hat aber einen gewissen Performance Impact, welcher um so schlimmer wird, je mehr verschachtelte Tabellen geladen werden. Das liegt daran, dass das generierte SQL ein JOIN über diese Tabellen ist. Haben wir nun 1 Customer mit 2 SalesOrderHeader mit jeweils 3 SalesOrderDetail, werden 1 * 2 * 3 = 6 Zeilen aus der Datenbank geladen. Jede dieser Zeilen hat für den Customer die gleichen Informationen und auch die SalesOrderHeader sind doppelt vertreten. Man kann sich das so vorstellen:
[Customer][SalesOrderHeader1][SalesOrderDetail1][Product1]
[Customer][SalesOrderHeader1][SalesOrderDetail2][Product2]
[Customer][SalesOrderHeader1][SalesOrderDetail3][Product3]
[Customer][SalesOrderHeader2][SalesOrderDetail4][Product4]
[Customer][SalesOrderHeader2][SalesOrderDetail5][Product5]
[Customer][SalesOrderHeader2][SalesOrderDetail6][Product6]

Join in Memory: Mehrere Abfragen

Um das Problem mit den redundanten Daten zu umgehen, machen wir einfach mehrere Abfragen, jeweils eine pro Tabelle, die wir benötigen.

var customers = context.Customer.ToDictionary(c => c.CustomerID);
var headers = context.SalesOrderHeader.ToDictionary(h => h.SalesOrderID);
var details = context.SalesOrderDetail.ToDictionary(d => d.SalesOrderDetailID);
var products = context.Product.ToDictionary(p => p.ProductID);
RelationshipFixup(customers, headers, details, products);
private static void RelationshipFixup(IDictionary<int, Customer> customers, IDictionary<int, SalesOrderHeader> headers, IDictionary<int, SalesOrderDetail> details, IDictionary<int, Product> products)
{
    foreach (var header in headers.Values)
    {
        header.Customer = customers[header.CustomerID];
        header.Customer.SalesOrderHeader.Add(header);
    }

    foreach (var detail in details.Values)
    {
        detail.SalesOrderHeader = headers[detail.SalesOrderID];
        detail.SalesOrderHeader.SalesOrderDetail.Add(detail);

        detail.Product = products[detail.ProductID];
        detail.Product.SalesOrderDetail.Add(detail);
    }
}

Sum is 353.504,37
Used 1025 SQL SELECT Statements
Get took 10563ms
Output took 40859ms

Huch, da ist aber etwas gewaltig schief gelaufen. Das Problem ist, dass wir die Tabellen alle auf demselben context Objekt holen. Standardmäßig ist Change Tracking und Proxy Creation im Entity Framework 6.x angeschaltet (in 7 ist es standardmäßig ausgeschaltet). Dies kostet umso mehr Performance, je mehr Daten in einem Context geladen werden.

Diesmal korrekt: Mehrere Abfragen

Um das Problem zu beheben, sorgen wir dafür, dass Change Tracking und Proxy creation ausgeschaltet werden. Wenn man dies tut, muss man sich im klaren darüber sein, dass man auch Lazy Loading ausschaltet. Hat man früher mit Lazy Loading gearbeitet, kann es nun zu Fehlern im Programm kommen, da nun alles explizit geladen werden muss. Dadurch erhält man allerdings die volle Kontrolle über sein Programm zurück;

IDictionary<int, Customer> customers;
using (var context = GetContextForGet(counter))
{
    customers = context.Customer.ToDictionary(c => c.CustomerID);
    var headers = context.SalesOrderHeader.ToDictionary(h => h.SalesOrderID);
    var details = context.SalesOrderDetail.ToDictionary(d => d.SalesOrderDetailID);
    var products = context.Product.ToDictionary(p => p.ProductID);

    RelationshipFixup(customers, headers, details, products);
}
private static EfPerformanceContext GetContextForGet()
{
    var context = new EfPerformanceContext();

    context.Configuration.AutoDetectChangesEnabled = false;
    context.Configuration.ProxyCreationEnabled = false;

    return context;
}

Sum is 353.504,37
Used 4 SQL SELECT Statements
Get took 1305ms
Output took 0ms

Wir sehen, dass zwar mehr SQL Select Abfragen, als bei Include verwendet wurden, aber der Abfragen insgesamt schneller waren. Um unsere Entity Framework Abfragen jetzt noch schneller zu machen, werden wir im nächsten Teil die Abfragen parallelisieren.

Warum dein Entity Framework langsam ist

Oft hört man ja, dass das Entity Framework zwar sehr komfortabel, aber auch sehr langsam sei. Bei Update und Insert Operationen ist dies tatsächlich der Fall und es gibt schon einige Frameworks, die sich Bulk Inserts und Updates angenommen haben. Wenn es allerdings um ein Get geht, ist der Flaschenhals fast immer der Programmierer vorm Bildschirm. Dieser Post soll meine Lernkurve von 2 Jahren Entity Framework Nutzung wiederspiegeln und hoffentlich einigen aufzeigen, wie sie ihre Abfragen zig-fach schneller machen können.
Weiterlesen

SignalR auf dem Arduino Part1: Das Protokoll verstehen

Für mein Smart-Home Projekt sollen einige Arduinos als Temperatursensoren zum Einsatz kommen.
Da als Bus SignalR genutzt wird, es aber für den Arduino noch keine SignalR Bibliothek gibt, muss ich selbst etws basteln.
Das Ergebnis soll hierbei nicht eine vollständige SignalR Implementation für den Arduino sein, sondern nur soweit funktionieren, dass ich mittels Websockets Nachrichten an den Server schicken kann, um so die Temperatur zu übermitteln.

Für den Arduino gibt es bereits HTTP, Websocket und JSON Bibliotheken.
Diese müssen aber zu einer SignalR Bibliothek verbunden werden.
Als erstes muss also das Protokoll verstanden werden.
Weiterlesen

Erste Schritte mit dem .Net Gadgeteer

Im Hinblick auf eine spätere Hausautomatisierung, habe ich mir vor kurzem ein .Net Gadgeteer zugelegt. Dies ist ein kleines Prototyping Kit, auf welchem das .Net Micro Framework läuft. Das .Net Micro Framework ist eine SEHR abgespeckte Version des .Net Frameworks. Es bietet aber natürlich die Vorteile einer Managed Runtime, wie z.B. Garbage Collection und ermöglicht so, mit C#, oder VB.Net auf einem Microcontroller zu programmieren, was schon ziemlich genial ist. Es wird für das Display sogar eine abgespeckte Version von WPF (leider ohne XAML Unterstützung) und der Client Teil von WCF angeboten.

Die Hardware ist Open Source und wird von GHI-Electronics hergestellt und vertrieben. Auf deren Webseite finden sich auch Ressourcen, ein Forum und weiterer Support zum .Net Gadgeteer. In Europa habe ich einen Distributor in Frankreich gefunden, Génération ROBOTS. Dort habe ich mir meine Teile bestellt.

Gadgeteer

Im Uhrzeigersinn, angefangen mit dem roten USB-Modul:

  • USB Client SP-Modul
  • ENC28 Ethernetmodul
  • Temperatur-Feuchtigkeitsmodul
  • RFID Lesemodul
  • microSD Kartenslotmodul
  • Farb-Sensormodul
  • Touchscreen-Farbdisplay TE35
  • Lichtsensormodul

Und natürlich das Mainboard, das FEZ Hydra.

Folgende Software wird benötigt und sollte Installiert, bevor man sein Gadgeteer das erste mal an den PC anschließt:

Als erstes muss man eine neue Software auf Board flashen. Hierzu verbindet man sein USB Modul mit einem D-Steckplatz und das wiederum mit einemComputer.

Das USB Modul erkennt man an seiner roten Farbe, denn beim Gadgeteer sind alle Stromliefernden Module rot. Auch ist auf jedem Modul ein Buchstabe, welcher angibt, welchen Steckplatz das Modul benötigt. Auf dem USB-Modul ist ein D aufgedruckt. Man muss also auf dem Mainboard ein Steckplatz suchen, welcher ebenfalls ein D aufweist. Hat ein Modul mehr, als einen Buchstaben, so muss der Steckplatz nur einen der Buchstaben aufweisen. Das Temperatur-Feuchtigkeitsmodul hat z.B. die Buchstaben X und Y aufgedruckt. Es benötigt also einen Steckplatz, auf dem entweder ein X, ein Y, oder beide aufgedruckt sind.

Das Board sollte nun vom PC erkannt werden und der Treiber sollte installiert werden. Ich werde hier spezifisch auf das FEZ Hydra Board eingehen. Wer ein anderes hat, benötigt ggf. eine andere Prozedur. Sollte das Board nicht starten, folgendes probieren: Das Board an ein USB Ladegerät anschließen, ggf. den Reset Knopf drücken. Warten, bis das Board gestartet ist und anschließend wieder an den PC anschließen.

Der erste Teil der benötigten Software auf dem Board ist der Loader (TinyBooter). Hierzu müssen wir unser Board in den Bootloader Modus versetzen. Am einfachsten geht dies, wenn man am Sockel 3 oder 4 die Pins 8 und 10 kurzschließt (Büroklammer o.ä.), auf den Resetknopf drückt, 5 Sekunden wartet und die Büroklammer wieder entfernt.

2594_large[1]

In der Systemsteuerung unter „Geräte und Drucker“ sollte nun unter „Nicht angegeben“ folgendes Gerät auftauchen: „GHI Boot Loader Interface (COM X)“ wobei X eine Zahl < 50 sein muss. Wird dort ein anderes COM Gerät angezeigt, so muss man manuell den Treiber ändern: Doppelklick > Hardware > Eigenschaften > Einstellungen ändern > Treiber > Treiber aktualisieren > Auf dem Computer nach Treibersoftware suchen. > Aus einer List von Gerätetreibern auf dem Computer auswählen > Haken weg bei Kompatible Hardware anzeigen > Hersteller: GHI Electronics, LCC > Modell: GHI Boot Loader Interface > Weiter > Ja. Ggf. ist ein Neustart nötig.

Jetzt startet man eine Eingabeaufforderung als Administrator (Windows-Taste > cmd eingeben > Rechtsklick > Als Administrator ausführen).

Navigiert in mit folgenden Befehlen in das korrekte Verzeichniss (C:\Program Files (x86)\GHI Electronics\GHI OSHW NETMF v4.2 SDK\FEZ Hydra\Firmware\FEZ Hydra TinyBooter Updater):

  • cd..
  • cd..
  • cd „Program Files (x86)“
  • cd „GHI Electronics“
  • cd „GHI OSHW NETMF v4.2 SDK“
  • cd „FEZ Hydra“
  • cd Firmware
  • cd „FEZ Hydra TinyBooter Updater“

Nun startet man die UpdateFEZHydra.bat mit dem Argument comX, wobei X durch den COM Port ersetzt wird, welchen man vorher erfahren hat (z.B.  UpdateFEZHydra.bat com3) und wartet solange, bis eine Textdatei aufploppt, die das Logfile enthällt. Sollte hier kein Erfolg verzeichnet sein, überprüft noch einmal genau alle vorangegangenen Schritte. Überprüft auch, dass euer USB Port genügend Strom lieferd (direkt an den Rechner anschließen, oder einen Powered Hub nutzen) und, dass ihr den korrekten COM Port angegeben habt.

Nachdem nun der Loader auf dem Mainboard ist und es neugestartet ist, sollte es als USB Gerät (FEZ Hydra) erkannt werden.

Jetzt muss noch die Firmware auf das Gerät geflashed werden. Dazu startet man das Programm FEZ Config, welches vorhin mitinstalliert wurde. Als Defice sollte hier ein FEZ Hydra_Gadgeteer erkannt werden. Jetzt geht man auf den Reiter Firmware Updater. Plant man, ein Ethernet Modul einzusetzen, so muss man dies an den Port 3 anschließen und den Haken bei „Check if you need an Ethernet Firmware“ setzen. Dies bedeutet gleichzeitig, dass das Board ohne angeschlossenes Ethernet Modul nicht mehr startet. Wichtig ist auch, dass man das Ethernet Modul später in Visual Studio nicht verbindet. (Irgendeine komische Eigenheit der Firmware). Jetzt klickt man auf Next, dann wieder auf Next und schließlich auf Ok. Nun wird die Firmware auf das Board geladen. Um zu prüfen, ob alles geklappt hat, geht man wieder auf den Reiter Connection und Pingt das Gerät einmal an. Als Antwort sollte TinyCLR zurückkommen.

Das war es nun soweit mit der Inbetriebnahme des Boards. Demnächst folgt ein Artikel zu einem ersten Programm.