Managed Code Performance und Test-Driven-Development

Florian Rappl, Universität Regensburg

Performance + TDD =

qualitativ hochwertige softwareentwicklung.

Performance Test

Agenda

  1. 09:00 - 10:15 Vortrag (Erster Teil)
  2. 10:15 - 10:45 Pause
  3. 10:45 - 12:00 Vortrag (Zweiter Teil)
  4. 12:00 - 13:00 Mittagspause
  5. 13:00 - 16:00 Übungen
  6. 16:00 - 17:00 Offener Teil

Übersicht

  1. Kurze Wiederholung: C# bzw. managed Code
  2. Typische Performance Probleme
  3. Tools zur Performanceanalyse
  4. Unit Tests mit Microsoft Visual Studio
  5. Test-Driven-Development
  6. RESTful APIs bauen
  7. REST mit WCF

Managed Code

  • Automatische Speicherverwaltung
  • GC räumt Speicher für uns auf
  • Unterscheidung Heap vs. Stack
  • Boxing / Unboxing
  • Finalisierer über IDisposable

Was zeichnet C# aus?

  • C++ ähnlicher Syntax
  • Konsistenz und lesbarkeit
  • Generische Typen und Methoden
  • Ko- und Kontravarianz
  • Lambda Ausdrücke
  • LINQ und Erweiterungsmethoden

Die größten Schwachstellen

  • LINQ Abfragen (mindestens ~15%)
  • Verwendung unnötig großer Objekte
  • GC Fragmentierung
  • Rekursive Funktionsaufrufe
  • Unnötige Allocations

Unnötige Allocations

  • Häufiges Boxing / Unboxing
  • Strings und string Manipulationen
  • Iteratoren
  • Delegaten and Lambda Ausdrücke
  • Kollektionen (Dictionary, etc.)
  • IDisposable Objekte
  • Nicht aufgeräumte Eventhandler

→ 01 - Allocations (LINQ)

... ist nun alles schlecht?

  • Language Features steigern Produktivität
  • Verbessern die Lesbarkeit
  • Sorgen für bessere Wartbarkeit
  • Daher ganz klar: Nein!
  • Code dort optimieren wo es sinnvoll ist

Boxing

  • Boxing verschwendet Speicher
  • Benötigt: Zusätzlicher Zeiger und Wert
  • Außerdem muss der GC aufräumen
  • Vorsicht gilt v.a. bei Überladungen
  • Niemals object als Übergabeparameter verwenden

→ 02 - Boxing

Optimierungszyklus

Optimization

Potentielle Verbesserungen

  • Caches ohne Discard-Policy
  • Referenzen auf temporäre Objekte
  • Niemals async void verwenden
  • Arrays eindampfen (n auf 1-dimensional)
  • Permanente Objekte vor den temporären alloziieren
  • Strukturen für kl. Objekte verwenden

Strukturen

  • Klassen bei x86 mindestens 12 Bytes
  • Objekte kleiner 16 Bytes sollten Strukturen sein
  • Strukturen liegen am Stack und nicht am Heap
  • Niemals mehr als 64 Bytes in Strukturen packen
  • Beim Boxing werden Strukturen auf den Heap gelegt
  • Allgemein gilt: Häufige Property Zugriffe vermeiden

→ 03 - Strukturen

Strings ...

  • ... sind spezialisierte Char-Arrays und
  • ... die Ursache der meisten Performance Probleme!
  • Ein Char in C# sind 2 Bytes (UTF-16)
  • Optimal: Verwenden von StringComparison.Ordinal
  • Die .NET Methoden sind nicht sehr schnell
  • Es kann sinnvoll sein eigene String Helfer zu schreiben
  • Viele String.Concat Aufrufe sind tödlich

Objekte wiederverwenden

  • Häufig kann man Objekte wiederverwenden
  • Dies spart Speicher
  • Sorgt für schnellere Verfügbarkeit
  • Verhindert GC Fragmentierung
  • Allerdings kann dies zu Wartungsproblemen führen
  • Und das Recycling muss auch verwaltet werden

StringBuilder Pooling

  • Beispiel für geschicktes Recycling
  • Ohne Pooling: 2-3 Concat Aufrufe sind OK
  • Mit Pooling: Bereits ab 2 Verknüpfungen besser
  • Am Besten [ThreadStatic] verwenden
  • Dies sorgt für Thread-lokale Buffer

→ 04 - StringBuilder Pooling

PerfView

  • Ein Tool zur Analyse von Application Performance
  • CPU, Managed Memory und Blocked Time Analyse
  • Verwendet ETW um Stack Dumps zu erstellen
  • Dokumentation ist ins Tool eingebaut
  • PerfView agiert systemweit
  • Für jede Art von GC Memory sinnvoll

PerfView Basics

  • Sowohl Bottom-up als auch Top-Down Approach möglich
  • Der Call-tree ist gut für Top-Down
  • Ansonsten das Call-by-name Tab verwenden
  • Wichtig: Immer die relevanten Daten betrachten
  • Und nur signifikante Daten verwenden (Metric/msec ≥ 0.5)
  • Mindestens 2000 Stack Dumps geben (je mehr umso besser)

Probleme mit PerfView

PerfView

→ 05 - PerfView

Visual Studio Analyzer

Visual Studio Analyzer

Direkter Einstieg

  • Übersichtsseite zeigt Hot Path
  • Außerdem CPU Auslastung per Chart
  • Grafische Navigation häufiger Methoden
  • Speicherbild nicht so detailliert wie PerfView
  • Direkte Integration in die Projektverwaltung
  • Anzeige im Codeeditor

Vergleich mit PerfView

  • Grundgerüst identisch
  • Möglichkeiten ein wenig eingeschränkt
  • Dafür aber bequemer (direkt aus VS)
  • Und hübscher / übersichtlicher dargestellt
  • Allerdings an VS gebunden / nicht sehr flexibel

→ 06 - Visual Studio Analyzer

Unit-Tests

  • Problem: Wie testet man Code?
  • Lösung: Schreiben Code (!) zum Testen von Code (...)
  • Einschränkungen: Testbarkeit, Verläßlichkeit, Abdeckung
  • Nur sehr einfache (d.h. ohne Logik) Tests
  • Nur Deterministische Codes können getestet werden
  • z.B. keine einfachen Tests für Zufallsgeneratoren

Unit-Test Ablauf

Unit-Test Principle

Code-Coverage

  • Gute Abdeckung (Code coverage) notwendig
  • Coverage gibt relative Zahl der genommenen Pfade an
  • Ein Codepfad ist ein möglicher Durchlauf der Logik
  • Gibt z.B. auch an wieviel Methoden verwendet wurden
  • Bevorzugt public Methoden testen
  • Ruhig mehrere Testabfragen pro Testmethode
  • Aber nie mehr als ein Szenario überprüfen

Test-Frameworks

  • Es gibt mehrere Frameworks um Unit-Tests durchzuführen
  • Eins der ältesten ist NUnit
  • Direkt ins Visual Studio ist MSTest integriert
  • Interessant: Speicherung der Testergebnisse
  • Können ins Build- oder VC-System integriert werden
  • Standardmäßige / geplante Ausführung möglich
  • Kein Commit ohne (neue und) erfolgreiche Tests

MSTest

  • Können Tests direkt im Visual Studio debuggen
  • Auslagerung der Tests in eine Bibliothek
  • Spezielle Attribute dekorieren die Tests
  • z.B. [TestMethod] für einen Test
  • Tests sind in Klassen mit [TestClass] integriert
  • Es wird eine neue Instanz pro Testmethode erzeugt
  • Statische Assert Klasse verwenden

→ 07 - MSTest

Test-Driven-Development (TDD)

  • TDD soll für saubere Codeentwicklung sorgen
  • Das Prinzip ist einfach aber ...
  • ... anfangs problematisch umzusetzen
  • Vor jeder neuen Methode soll ein Test (ent)stehen
  • Neue Features können nur durch neue Tests implementiert werden
  • Wichtigstes Prinzip: Red-Green-Refactor

Red-Green-Refactor

Red Green Refactor TDD

Vor- und Nachteile

  • Sehr hohe Codequalität
  • Code leicht überprüfbar
  • Fehler schneller auffindbar
  • Vermeidung von Fehlern in Tests
  • Feature-Konflikte erkennbar
  • Längere Entwicklungszeit
  • Große Disziplin notwendig
  • u.U. schwierig zu realisieren
  • Fehlender Test? Risiko!
  • Schützt nicht vor Designfehlern

→ 08 - TDD

Representational State Transfer (REST)

  • Nutzt das HTTP Protokoll aus
  • Idee: Eine URL ist das Ergebnis einer Operation
  • (CRUD) Operationen werden durch HTTP Methoden markiert
  • Rückgaben (OK, Fehler, ...) über Status-Codes
  • Mögliche Datenformate u.a. XML und JSON
  • Ermöglicht Entwicklung standardisierter APIs
  • Keine Vorschrift über die Implementierung

Eigenschaften von REST

  • Adressierbarkeit (eindeutige Adresse)
  • Unterschiedliche Repräsentationen (z.B. XML, JSON)
  • Zustandslosigkeit (keine dauerhafte Verbindung)
  • Operationen (GET, PUT, POST, DELETE)
  • Verwendung von Hypermedia (z.B. Referenzen in Daten)

StatusCode Top 7

HTTP StatusCode

RESTful APIs nutzen und bauen

  • Einzige Voraussetzung: Das HTTP-Protokoll
  • Einige Frameworks sind bereits gut ausgestattet
  • Beispiel: ASP.NET MVC 4 mit integrierter Web API
  • Ansich alles mit ASP.NET (MVC) leicht möglich
  • Allerdings: MVC 4 bietet eine standardisierte Schnittstelle
  • Vorteil für uns ist die ultra schnelle Entwicklung

WebAPI

  • Die Web API ermöglicht einen zentralen Zugriff
  • Die Daten werden je nach Anfrage in JSON oder XML geliefert
  • Müssen nur von ApiController erben
  • Die Methoden heißen dann Get, Post, ...
  • Auch OData Queries sind möglich ([Queryable])
  • Auch Paging kann sehr leicht integriert werden

→ 09 - WebAPI mit ASP.NET MVC 4

Windows Communication Foundation (WCF)

  • Nachfolger von Webservices
  • Ermöglicht Service-Oriented-Architecture (SOA)
  • Vereinigt .NET Remoting, Webservices, COM+, ...
  • Idee: Services über sog. Contracts aufbauen
  • Definition von Endpunkten, API und Protokoll
  • Zentrales Konzept: Behaviors

WCF Dienste

WCF Services

REST mit WCF

  • Müssen nur HTTP als Endpunkt-Protokoll verwenden
  • Wichtig: Methoden müssen richtig deklariert sein
  • Deklaration über [WebInvoke] im Contract
  • Die Web.config muss entsprechend angepasst werden
  • Als Endpunkt-Behavior nimmt man <webHttp /> mit

Das richtige Format ...

  • Problem: Unterschiedliche Pfade für mehrere Formate benötigt
  • Die Lösung ist elegant: eigene WebHttpBehavior Implementierung
  • Müssen den Accept Header untersuchen
  • Ausgabe erfolgt dann im akzeptierten Format
  • Somit wird das Format über den HTTP-Header, anstelle der URL bestimmt

WCF REST konsumieren

  • Wichtig: Zum Senden das richtige Format (z.B. application/xml) einstellen
  • Ansonsten keine automatische Erkennung
  • Im Contract [XmlSerializerFormat] verwenden
  • Ebenfalls Sinnvoll: BodyStyle = WebMessageBodyStyle.Bare
  • Zugriff auf Ausgabe über WebOperationContext.Current
  • z.B. StatusCode setzen in OutgoingResponse.StatusCode

→ 10 - REST mit WCF

Viel Erfolg!

MVP

Florian Rappl, Universität Regensburg