Performance: introductie

Door Sijmen J. Mulder

Efficiëntie en snelheid

Bij het denken over performance moet onderscheid worden gemaakt tussen efficiëntie en snelheid. Efficiëntie is het minimaliseren van het werk dat gedaan moet worden.

Een voorbeeld van verbeterde efficiëntie in dit project was het beperken van het aantal doorgerekende tijdstappen in berekeningen. Het gebruiken van arrays in plaats van dictionaries in resultaatobjecten was een snelheidsverbetering. Technisch gezien was daarbij ook sprake van vermeden werk, namelijk dictionarylookups, maar dat laat vooral zien dat dit onderscheid verschuift op basis van het abstractieniveau.

Bij het verbeteren van performance kijken we naar beide aspecten, elk met de tools die daarvoor geschikt zijn.

Profiling

De primaire meetmethode hier gebruikt is profiling. Hierbij wordt het programma aangepast of door een extern programma geïnspecteerd om gedetailleerde metingen te doen naar hoe vaak en hoe lang delen van de code (modules, functies) uitgevoerd worden.

De belangrijkste profilingmethoden zijn:

Profiling beperkt zich niet tot processorgebruik. Het kan ook worden gebruik voor bijvoorbeeld geheugenallocaties of I/O.

Top-down en bottom-up

De resultaten van profiling kunnen primair op twee verschillende manieren gegeven worden: top-down en bottom-up. Top-down uitvoer ziet er bijvoorbeeld zo uit:

  - main()     (100%)
    - load()    (34%)
      - ...
    - run()     (55%)
      - foo()   (10%)
      - bar()   (40%)
      - baz()   (15%)
    - report()  (11%)
      - ..

Deze profile laat een duidelijke hot path zien naar de bar() functie. Wellicht kan deze efficienter worden gemaakt.

Als de uitvoer bottom-up wordt weergeven, zou dat er zo uit kunnen zien:

 - alloc()  (14%)
 - pow()     (8%)
 - print()   (2%)
 - ...

Deze uitvoer wijst op iets heel anders: er wordt veel tijd besteed aan allocaties en machtsverheffingen. Het kan lonen om uit te zoeken waarom dat is en of dat vermeden kan worden, of dat de functies zelf efficienter worden gemaakt.

Wat dit voorbeeld ook laat zien is dat de twee benaderingen verschillende pijnpunten identificeren. Top-down profiles geven vooral aanknopingspunten voor efficiëntieverbetering op hogere niveaus van abstractie, terwijl bottom-up profiles ‘papercuts’ (verborgen kosten) op lager niveau laat zien die uit de top-down profiles niet zichtbaar zijn.

Lowlevel

Andere verborgen kosten worden een niveau lager gemaakt: op het niveau van processorinstructies, geheugentoegang, contextswitches enz. Deze zijn niet zichtbaar op het niveau van functies en hoger waarop profiling meestal gebeurt. Toch kan de impact van deze zaken op kritieke codepaden groot zijn.

Een aantal belangrijke lowlevel performance-aspecten zijn:

Vuistregels

Bovenstaande geeft al wat aanknopingspunten voor de keuze van code- en datastructuren. Meten is weten, maar een paar nuttige vuistregels kunnen zijn:

In het algemeen is dat data oriented programming-benadering, die aansluit bij deze vuistregels, het meest geschikt voor kritieke code.

Maar: meten is weten, en de eerste stap moet altijd efficiëntie zijn. Code die niet wordt uitgevoerd is het snelst. Lowlevel optimalisatietechnieken zijn pas nodig als er op hoog niveau geen grote winst meer te halen is.

Benchmarking

Zoals genoemd is meten weten. Bij optimalisatie is het daarom het beste om een sectie code te nemen, bijvoorbeeld een representatieve set berekeningen, en die als benchmark te gebruiken. Profile deze benchmark, probeer verbeteringen, en profile opnieuw. “Beproeft alle dingen; behoud het goede.”

Merk op dat just-in-time-compilatie, caching, paging, branchprediction, enz. ervoor kan zorgen dat de eerste aantal runs niet representatief zijn. Het systeem moet opwarmen. Daarnaast is er altijd een marge van willekeur. Gebruik geen voldoende groot aantal iteraties en het liefst een benchmarkframework zoals BenchmarkDotNet.

Tools

Instrumentatie en sampling kan uitstekend met de ingebouwde profiler van Visual Studio (Alt+F2).

Voor handmatige profiling kan de klasse System.Diagnostics.Stopwatch worden gebruikt. Deze heeft hogere precisie dan System.DateTime.

Voor inzicht gebaseerd op performancecounters van de processor is Intel VTune een aanrader. Deze laat zaken als cachemissers en pagefaults zien, gekoppeld aan de code in kwestie.

Terug naar boven