Každý vývojár, ktorý sa zaoberá optimalizáciou výkonu, skôr alebo neskôr natrafí na na triedu StopWatch nachádzajúcej sa v mennom priestore System.Diagnostics. V prípade, ak ste už niekedy robili testy výkonu, určite ste si už stihli všimnúť že výsledky sa môžu líšiť až o 25%-30%. V tomto článku vám ukážem, ako optimálne navrhovať jednovláknové testovacie programy tak, aby rozdiel vo výsledkoch bol v rozmedzí 0.1%-0.2%.
V dnešnej dobe majú moderné počítače procesory s viacerými jadrami, veľkou cache atď. a inými vecami, ktoré môžu výrazne ovplyvniť čas vykonávania algoritmu. Tzv. White Box techniky s attachnutym debuggerom výrazne skresľujú výsledky testov a spomaľujú vykonávanie algoritmu, pretože CPU je zaťažované väčším množstvom inštrukcií. Na druhej strane Black Box techniky s neattachnutým debuggerom nám poskytujú ďaleko viac relevantných informácií.
Jedna z najdôležitejších vecí je zabezpečiť, aby sa funkcia vykonávala práve na jednom jadre procesora. To môžeme docieliť nastavením vlastnosti ProcessorAffinity nášho procesu:
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);
V ďalšom kroku musíme zabezpečiť, aby ostatné vlákna nevyužívali naše jadro/procesor. Toho docielime tak, že nastavíme nášmu vláknu najvyššiu prioritu a ostaným nižšiu:
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Ešte pred začatím samotného testovania, je nutné spraviť tzv. zahrievacie kolo (Warmup), po ktorom by mali byť výsledky už viac menej stabilné. V nasledujúcom príklade som zvolil dĺžku Warmup fázy 1200 ms (dĺžka warmupu sa môže na rôznych počítačoch meniť).
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < 1200)
{
result = TestFunction(seed, count);
}
stopwatch.Stop();
Kompletný príklad:
using System;
using System.Diagnostics;
using System.Threading;
namespace PreciseMeasure
{
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
long seed = Environment.TickCount; long result = 0;
int count = 100000000;
Console.WriteLine("20 testov bez korektnej prípravy");
Console.WriteLine("Warmup");
for (int repeat = 0; repeat < 20; ++repeat)
{
stopwatch.Reset();
stopwatch.Start();
result ^= TestFunction(seed, count);
stopwatch.Stop();
Console.WriteLine("Ticks: " + stopwatch.ElapsedTicks +" mS: " + stopwatch.ElapsedMilliseconds);
}
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; Thread.CurrentThread.Priority = ThreadPriority.Highest;
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("20 testov s korektnou pripravou");
Console.WriteLine("Warmup");
stopwatch.Reset();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < 1200)
{
result = TestFunction(seed, count); // Warmup
}
stopwatch.Stop();
for (int repeat = 0; repeat < 20; ++repeat)
{
stopwatch.Reset();
stopwatch.Start();
result ^= TestFunction(seed, count);
stopwatch.Stop();
Console.WriteLine("Ticks: " + stopwatch.ElapsedTicks +
" mS: " + stopwatch.ElapsedMilliseconds);
}
Console.WriteLine(result);
}
public static long TestFunction(long seed, int count)
{
long result = seed;
for (int i = 0; i < count; ++i)
{
result ^= i ^ seed;
}
return result;
}
}
}