At one point, I wanted to figure out what was slowing down a program that I largely had not written. I wanted to be able to time multiple nested pieces, and multiple sequential pieces, all at the same time. I wanted the timing to be pretty accurate, which meant using the Stopwatch object and also meant that the code had to have little time impact on its own, and I wanted it to be cheap. Naturally, you can buy commercial profilers, and there are some in some versions of VB. I didn't have access to either of those when I wrote this, and, quite frankly, the ease of use is such that I still rather like it.
Here's the code. Explanation will follow:
There is little to this that needs much explanation. The whole thing is a Singleton class with one significant method: Track.Code:Public Class ProfileTimer Private mStop As Stopwatch Private mRegister As System.Collections.Generic.List(Of TimeHolder) Private mIsRunning As Boolean Private mLock As Boolean Public Shared TheTime As New ProfileTimer Private Sub New() mStop = New Stopwatch mRegister = New System.Collections.Generic.List(Of TimeHolder) mIsRunning = False mLock = False End Sub #Region "Properties" Public ReadOnly Property GetString(ByVal index As Integer) As String Get If index >= 0 AndAlso index < mRegister.Count Then Return mRegister(index).TokenName & ": " & (mRegister(index).StopTime - mRegister(index).StartTime).ToString & "ms" Else Return "" End If End Get End Property Public ReadOnly Property Count() As Integer Get Return mRegister.Count End Get End Property Public ReadOnly Property GetString(ByVal tokn As String) As String Get Dim n As Integer = GetIndex(tokn) If n >= 0 Then Return mRegister(n).TokenName & ": " & (mRegister(n).StopTime - mRegister(n).StartTime).ToString & "ms" Else Return "" End If End Get End Property Public ReadOnly Property GetString() As String Get If mRegister.Count = 0 Then Return "No Timing" Else Dim x As Integer Dim sb As New System.Text.StringBuilder For x = 0 To mRegister.Count - 1 sb.Append(mRegister(x).TokenName & ": " & (mRegister(x).StopTime - mRegister(x).StartTime).ToString & "ms" & Environment.NewLine) Next Return sb.ToString End If End Get End Property Public Property Lock() As Boolean Get Return mLock End Get Set(ByVal value As Boolean) mLock = value End Set End Property #End Region #Region "Methods" Public Sub StopAll() Dim x As Integer Dim th As TimeHolder For x = 0 To mRegister.Count - 1 If mRegister(x).StopTime = -1 Then th = mRegister(x) th.StopTime = mStop.ElapsedMilliseconds mRegister(x) = th End If Next End Sub Private Function StopTrack(ByVal tokn As String) As Long Dim n As Integer = GetIndex(tokn) Dim th As TimeHolder If n >= 0 Then th = mRegister(n) th.StopTime = mStop.ElapsedMilliseconds mRegister(n) = th Return mRegister(n).StopTime - mRegister(n).StartTime End If End Function Private Sub StartTrack(ByVal tokn As String) mRegister.Add(New TimeHolder(tokn, GetTick)) End Sub Public Sub Track(ByVal tokn As String) Dim n As Integer = GetIndex(tokn) If n = -1 Then StartTrack(tokn) ElseIf mRegister(n).StopTime >= 0 Then If Not Lock Then mRegister.RemoveAt(n) StartTrack(tokn) End If Else StopTrack(tokn) End If End Sub Private Function GetTick() As Long If mIsRunning Then Return mStop.ElapsedMilliseconds Else mIsRunning = True mStop.Start() Return 0 End If End Function Private Function GetIndex(ByVal tokn As String) As Integer Dim x As Integer For x = 0 To mRegister.Count - 1 If mRegister(x).TokenName = tokn Then Return x End If Next Return -1 End Function #End Region End Class Public Structure TimeHolder Implements IEquatable(Of String) Public StartTime As Long Public StopTime As Long Public TokenName As String Public Sub New(ByVal tokn As String, ByVal sTime As Long) StartTime = sTime TokenName = tokn StopTime = -1 End Sub Public Function Equals1(ByVal other As String) As Boolean Implements System.IEquatable(Of String).Equals Return (other = TokenName) End Function End Structure
To use the class, copy it somewhere in the project. You access the Singleton via: ProfileTimer.TheTime
A timed segment is called a track, and each track has a 'name', which can be any string. To start timing a segment, call Track() while passing in a name. Calling Track a second time with the same name will stop the segment. Therefore, you can write code like this:
That's a pretty silly example, because track "b" will just be starting and stopping with each iteration, while track "a" is really timing almost the same thing, but it does give some idea of what can be done with the timer. A more complete example would be tedious.Code:Public Sub Foo() 'Start track "a" to time the whole loop. ProfileTimer.TheTime.Track("a") For x as Integer = 0 To 1000 'Start track "b" to time just this call. ProfileTimer.TheTime.Track("b") Bar() 'Stop track "b". ProfileTimer.TheTime.Track("b") Next End Sub
At any time, you can call StopAll to stop all running tracks, and you can call one of the GetString properties to see the result. If you pass in no arguments to GetString, it will return the time for each track, with one track per line. Alternatively, you can get the time for a single track by passing in the track name, or by passing in the index into the track list.
There is some other functionality in there about locking and unlocking tracks. Frankly, I wrote this so long ago that I don't remember how that works, but it has to do with stopping and starting tracks, and I have never actually used it. The rest works well. That part....well, who knows, and frankly, who cares. Excess functionality with little utility.


Reply With Quote