I have an Excel document with some VBA scripts.
I'm using 'Timer' to measure execution times of my code.
I am not able to measure how long it takes to add an entry to a Dictionary. The result is 0.
Are there more precise mechanisms available that allow me to measure how long this takes?
At the top of the script you can add this:
Private Declare PtrSafe Function get_frequency Lib "kernel32" _
Alias "QueryPerformanceFrequency" ( _
ByRef Frequency As Currency) _
As Long
Private Declare PtrSafe Function get_time Lib "kernel32" _
Alias "QueryPerformanceCounter" ( _
ByRef Counter As Currency) _
As Long
Then in your code you can get high precision time measurements like this...
' run this once
Dim per_second As Currency: get_frequency per_second
' ...
' get start time
Dim t0 As Currency: get_time t0
' HERE perform the operations you want to time
' ...
' get end time
Dim t1 As Currency: get_time t1
' calculate high precision duration
Dim elapsed_millis As Double: elapsed_millis = (t1 - t0) / per_second * 1000#
Add this at the start of your subroutine:
Dim StartTime As Double 'Declaration of timer to identify length of runtime
StartTime = Timer
At this at the end of your subroutine:
Debug.Print "RunTime for Program is: " & Format((Timer - StartTime) / 86400,
"hh:mm:ss.ms") 'Print out runtime for subroutine
I have a large amount of data to scroll through every day and an autoscroll macro that pauses when a key is pressed (and resumes with a button push) would be a big help.
So far, I've tried:
Sub Autoscroll()
Dim RowCount As Integer
Dim i As Integer
RowCount = Range("Table").Rows.Count
For i = RowCount + 1 To 2 Step -1
Range("A" & i).Select
Application.Wait (Now + TimeValue("0:00:01"))
Next i
End Sub
But this doesn't achieve what I want for a few reasons:
It doesn't pause when I press a key
It can't go faster than 1 second. (I could use the Sleep function to make the scroll move faster)
Looking for some recommendations about the best way to do this.
Thank you
If you insist on using a macro try this, it should do the trick (if you are using Windows!).
You have to press the return key to interrupt. If you'd prefer a different key let me know.
Option Explicit
#If VBA7 Then
Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#Else
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#End If
'Sub pausing code execution without freezing the app or causing high CPU usage
'Author: Guido Witt-Dörring, https://stackoverflow.com/a/74387976/12287457
Public Sub WaitSeconds(ByVal seconds As Single)
Const VK_RETURN = &HD
Dim currTime As Single, endTime As Single, cacheTime As Single
currTime = Timer(): endTime = currTime + seconds: cacheTime = currTime
Do While currTime < endTime
If GetAsyncKeyState(VK_RETURN) Then
Sleep 200
Do Until GetAsyncKeyState(VK_RETURN)
DoEvents: Sleep 15
Loop
Sleep 200
End If
DoEvents: Sleep 15: currTime = Timer()
'The following is necessary because the Timer() function resets at 00:00
If currTime < cacheTime Then endTime = endTime - 86400! 'seconds per day
cacheTime = currTime
Loop
End Sub
Sub Autoscroll()
Dim RowCount As Long
Dim i As Long
RowCount = Range("Table").Rows.Count
For i = RowCount + 1 To 2 Step -1
WaitSeconds 0.5 '<-- this is how long it waits at every row,
Range("A" & i).Select 'set it to your desired value
Next i
End Sub
I hope you can help, I've moved over to a Windows 10 machine 64bit and one of the codes I used to interface with Attachmate no longer works as it is now reflection attachmate
The original code is
Private Declare Function GetTickCount Lib "kernel32" () As Long
Function screenCheckOK() As Boolean
' will wait for screen to become free. if not free after
' 30 seconds will return false
Dim screenStatus As Integer
Dim Start As Long
screenCheckOK = False
Start = GetTickCount()
Do
If GetTickCount() >= Start + 30000 Then Exit Function
DoEvents
screenStatus = getScreenStatus
Loop Until screenStatus = 0
screenCheckOK = True
End Function
Obviously I can't use GetTickCount I now have to use
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long).
But I just can't seem to write a new code for it. I thought I could use 'Sleep200 but that doesn't work.
any help please?
I am looking for a solution to call a MySub every 100 milliseconds.
MySub contains a loop with a variable length (and variable execution time).
Currently I can do this every second with this code:
Sub UPDATECLOCK()
Call MySub
NextTick = Now + TimeValue("00:00:01")
Application.OnTime NextTick, "UPDATECLOCK"
End sub
I read in the first answer in this post that it is not possible:
VB Excel Macro Millisecond Timing
Although in the second answer this solution is given:
Sub Macro2()
' Macro2 Macro
Range("C1").FormulaR1C1 = "1"
Application.Wait (Now + TimeValue("0:00:01")) '1sec
Range("C1").FormulaR1C1 = "2"
Application.Wait (Now + 0.000001) '1/10sec
Range("C2").Select
Sleep (100)
End Sub
If I understand correctly Application.Wait is added to let Excel wait for several milliseconds.
https://learn.microsoft.com/en-us/office/vba/api/excel.application.wait
Although the Excel application is frozen while using Wait which is not feasible for me.
Maybe an alternative could be to measure the time my loop takes to execute on the system clock.
I found this website https://www.aeternus.sg/best-millisecond-timer-vba/ with this code:
' Performance counter API's
#If VBA7 And Win64 Then
'for 64-bit Excel
Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
#Else
'for 32-bit Excel
Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
#End If
'Purpose: Measure elapsed time in milliseconds
Sub TimeACode()
Dim curFrequency As Currency
Dim curStartPerformanceCounter As Currency
Dim curEndPerformanceCounter As Currency
Dim lgResult As Long
'obtain no: of counts per second
lgResult = QueryPerformanceFrequency(curFrequency)
If lgResult > 0 Then
Debug.Print "Frequency: " & curFrequency
End If
'measure start count
lgResult = QueryPerformanceCounter(curStartPerformanceCounter)
If lgResult > 0 Then
Debug.Print "Start Count: " & curStartPerformanceCounter
End If
'*****************************************
'Insert the code to measure elapsed time
'*****************************************
'measure end count
lgResult = QueryPerformanceCounter(curEndPerformanceCounter)
If lgResult > 0 Then
Debug.Print "End Count: " & curEndPerformanceCounter
End If
'measure elapsed time
Debug.Print "Elapsed time (ms): " & (curEndPerformanceCounter - curStartPerformanceCounter) / curFrequency
End Sub
I do not understand this code very well. Although it seems to me it counts the processor ticks since the last reboot of the system. Would it be possible to alter this code in a way that it will call MySub after a certain number of ticks of the processor?
If I call this function on my system:
lgResult = QueryPerformanceFrequency(curFrequency)
The function returns 1000.
Does this mean my processor makes 1000 ticks per second?
Would it be possible to call MySub after every 100 ticks?
Thanks a lot!
Since performance of string concatenation is quite weak in VB6 I'm testing several StringBuilder implementations. To see how long they're running, I currently use the built-in
Timer
function which only gives me the number of seconds that have passed after midnight.
Is there a way (I guess by importing a system function) to get something with milliseconds precision?
Yes, you can use the Win32 API:
DWORD WINAPI GetTickCount(void);
To import it in VB6 declare it like this:
Private Declare Function GetTickCount Lib "kernel32" () As Long
Call it before the operation and after and then calculate the difference in time passed.
Put the following code in a Stopwatch class:
Option Explicit
Private Declare Function QueryPerformanceCounter Lib "Kernel32" (X As Currency) As Boolean
Private Declare Function QueryPerformanceFrequency Lib "Kernel32" (X As Currency) As Boolean
Private m_startTime As Currency
Private m_freq As Currency
Private m_overhead As Currency
Public Sub start()
QueryPerformanceCounter m_startTime
End Sub
Public Function ElapsedSeconds() As Double
Dim currentTime As Currency
QueryPerformanceCounter currentTime
ElapsedSeconds = (currentTime - m_startTime - m_overhead) / m_freq
End Function
Public Function ElapsedMilliseconds() As Double
ElapsedMilliseconds = ElapsedSeconds * 1000
End Function
Private Sub Class_Initialize()
QueryPerformanceFrequency m_freq
Dim ctr1 As Currency
Dim ctr2 As Currency
QueryPerformanceCounter ctr1
QueryPerformanceCounter ctr2
m_overhead = ctr2 - ctr1
End Sub
You can use it as follows:
Dim sw as StopWatch
Set sw = New StopWatch
sw.Start
' Code you want to time
Debug.Print "Code took " & sw.ElapsedMilliseconds " ms"
You might also consider using a different approach. Try calling your routines from a loop with enough iterations to give you a measurable time difference.
You can use two Win32 APIs:
QueryPerformanceCounter: To get the count at the start and end of the event.
QueryPerformanceFrequency: To get the number of ticks per second.
These use LARGE_INTEGER to represent 64 bit numbers.
There's code and an explanation in the MSDN KB article Q172338 How To Use QueryPerformanceCounter to Time Code
There's a Thomas Edison story, where he's interviewing some prospective engineers.
He asks them to determine the volume of a light bulb. Candidate A measures it and then uses the formula for the volume of a sphere, and another formula for the volume of the neck, and so on. Candidate B fills it with water and pours it into a measuring cup. Who do you think got the job?
Run it 1000 times and look at your watch before and after. Seconds = milliseconds.
I always use this in a module somewhere (could be in a class though). This code allows you to maintain up to six timers, with high accuracy:
Option Explicit
Private Declare Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
Private Declare Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private cFrequency As Currency
Private cCounters(0 To 5) As Currency
Public Sub StartCounter(Optional lCounterIndex As Long)
QueryPerformanceFrequency cFrequency
QueryPerformanceCounter cCounters(lCounterIndex)
End Sub
Public Function GetCounter(Optional lCounterIndex As Long) As Double
Dim cCount As Currency
QueryPerformanceFrequency cFrequency
QueryPerformanceCounter cCount
GetCounter = Format$((cCount - cCounters(lCounterIndex) - CCur(0.0008)) / cFrequency, "0.000000000")
End Function
Public Function Scientific(ByVal dValue As Double) As String
Dim lMultiplier As Long
Dim vNames As Variant
lMultiplier = 5
vNames = Array("peta", "tera", "giga", "mega", "kilo", "", "milli", "micro", "nano", "pico", "femto")
If Abs(dValue) < 1 Then
While Abs(dValue) < 1
dValue = dValue * 1000
lMultiplier = lMultiplier + 1
Wend
ElseIf Abs(dValue) >= 1000 Then
While Abs(dValue) >= 1000
dValue = dValue / 1000
lMultiplier = lMultiplier - 1
Wend
End If
Scientific = Format$(dValue, "0.000") & " " & vNames(lMultiplier)
End Function
You can try using the System::Diagnostics::Stopwatch
Imports System.Diagnostics
Dim sw As New Stopwatch()
sw.Start()
// do something
LOG("Elapsed " + sw.ElapsedMilliseconds + " ms")