Una delle esigenze più sentite - e più importanti - quando si scrive codice è quella di trovare il modo di svolgere l'attività richiesta garantendo le migliori prestazioni possibili. Per far questo è indispensabile dotarsi di uno strumento che ci consentano di misurare il tempo di esecuzione di ciascuna operazione o blocco di operazioni, possibilmente in unità di tempo adeguate (millisecondi o nanosecondi) e con una precisione il più possibile accurata.
Ovviamente, tale strumento deve dare la possibilità di misurare più operazioni all'interno del medesimo contesto di esecuzione, in modo che sia possibile definire vari lap e confrontarli tra loro: insomma, stiamo parlando in buona sostanza di qualcosa che fornisca le funzionalità tipiche di un comune cronometro.
Con questo obiettivo in mente alcuni anni fa ho sviluppato una classe che consente di fare esattamente questo. Il suo nome è TimeTracker e può essere utilizzata per creare un numero n di lap nel corso dell'esecuzione di un qualsiasi programma. Ciascun lap, una volta creato, può essere confrontato con qualsiasi altro lap sulla base di uno dei formati temporali disponibili: nanosecondi, microsecondi, millisecondi, secondi, minuti o ore.
Questo è un esempio di utilizzo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public ActionResult MyActionMethod() { TimeTracker tt = new TimeTracker("start"); // todo: insert some time-intensive piece of code here tt.AddLap("myLap1"); // todo: insert another time-intensive piece of code here tt.AddLap("myLap2"); // todo: insert yet another time-intensive piece of code here tt.AddLap("myLap3"); // store the timings to ViewBag ViewBag.myLap1Result = "myLap1 completed after " + GetLapTimeDiff("start", "myLap1") + " ms"; ViewBag.myLap2Result = "myLap2 completed after " + GetLapTimeDiff("start", "myLap2") + " ms"; ViewBag.myLap3Result = "myLap3 completed after " + GetLapTimeDiff("start", "myLap3") + " ms"; return View(); } |
L'esempio è relativo a una classica applicazione ASP.NET MVC, ma la classe può essere facilmente utilizzata in qualsiasi altro contesto. E' anche possibile instanziare l'oggetto globalmente - ad esempio nella classe Startup di un progetto ASP.NET 4 / ASP.NET Core - e utilizzarlo per effettuare misurazioni in punti diversi e/o lungo l'intero ciclo vita dell'applicazione.
Questo è il codice sorgente completo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
using System; using System.Collections.Generic; namespace Ryadel.Components.Diagnostics { /// <summary> /// A small object to measure the time between two or more operations. /// </summary> public class TimeTracker { #region Nested Types public enum TimeFormat { Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours } #endregion Nested Types #region Private Fields private Dictionary<string, long> m_TrackList; #endregion Private Fields #region Constructor public TimeTracker() : this(null) { } public TimeTracker(string lapName) { if (TrackList == null) m_TrackList = new Dictionary<string, long>(); if (!string.IsNullOrEmpty(lapName)) AddLap(lapName); } #endregion Constructor #region Methods /// <summary> /// Adds a lap to the TimeTracker /// </summary> /// <param name="lapName"></param> public void AddLap(string lapName) { TrackList[lapName] = GetCurrentTime(); } /// <summary> /// Gets the TimeDiff between the two given laps in time units specified by the given [TimeTracker.TimeFormat] /// </summary> /// <param name="startLap">name of the starting lap</param> /// <param name="endLap">name of the ending lap</param> /// <param name="format">a valid [TimeTracker.TimeFormat] enum value</param> /// <returns>a decimal representing the TimeDiff in time units specified by the given [TimeTracker.TimeFormat].</returns> public decimal GetLapTimeDiff(string startLap, string endLap, TimeFormat format = TimeFormat.Milliseconds) { if (!TrackList.ContainsKey(startLap)) throw new Exception(String.Format("Starting Lap '{0}' cannot be found.", startLap)); if (!TrackList.ContainsKey(endLap)) throw new Exception(String.Format("Ending Lap '{0}' cannot be found.", endLap)); return CalculateTime(TrackList[endLap] - TrackList[startLap], format); } protected virtual decimal CalculateTime(long tDiff, TimeFormat format = TimeFormat.Milliseconds) { switch (format) { case TimeFormat.Nanoseconds: return (decimal)(tDiff * 100); case TimeFormat.Microseconds: return (decimal)(tDiff / (long)10); case TimeFormat.Milliseconds: default: return (decimal)(tDiff / (long)10000); case TimeFormat.Seconds: return (decimal)(tDiff / (long)10000000); case TimeFormat.Minutes: return (decimal)(tDiff / (long)600000000); case TimeFormat.Hours: return (decimal)(tDiff / (long)36000000000); } } /// <summary> /// Protected (overridable) method to get the current lap time. /// </summary> /// <returns></returns> protected virtual long GetCurrentTime() { return DateTime.UtcNow.ToFileTimeUtc(); } /// <summary> /// Protected method to check if given lap name exists. /// </summary> /// <param name="lapNames"></param> protected void CheckLapNames(params string[] lapNames) { foreach (string n in lapNames) if (!TrackList.ContainsKey(n)) throw new Exception(String.Format("Lap '{0}' cannot be found.", n)); } #endregion Methods #region Properties /// <summary> /// Returns TRUE if the TimeTracker's TrackList is Empty, FALSE otherwise. /// </summary> public bool IsEmpty { get { return (TrackList == null || TrackList.Count == 0); } } /// <summary> /// Dictionary containing the laps (if any) /// </summary> protected Dictionary<string,long> TrackList { get { return m_TrackList; } } #endregion Properties } } |
La classe è interamente commentata, quindi non c'è bisogno di entrare ulteriormente nel dettaglio. Nonostante si tratti di un codice scritto ormai diversi anni fa, continua a rivelarsi ancora oggi uno strumento estremamente utile per analizzare in modo accurato le performance di qualsiasi tipo di codice sorgente scritto in C#.
Per il momento è tutto: felice Time Tracking!