Testing GetTickCount64() vs GetMicrosecondCount()

 

A simple attempt to synchronize the OnTimer() call to TimeLocal to be called at around the next second has revealed a question, I cannot wrap my head around.

So I tried to analyze the behaviour and found that GetTickCount64() and GetMicrosecondCount() give confusing results, as can be seen in the screenshot.

Here is the code to reproduce the output:

//+------------------------------------------------------------------+
//| Expert timer function                                            |
//+------------------------------------------------------------------+
void OnTimer()
{
    printf("OnTimer()");
    ulong enter = GetTickCount64();
    ulong enter_mc = GetMicrosecondCount();


        ulong mics = GetMicrosecondCount();
        while(mics + 500000 > GetMicrosecondCount());
        //Sleep(500);
        mics = GetMicrosecondCount() - mics;


    ulong exit_mc = GetMicrosecondCount();
    ulong exit = GetTickCount64();

    printf("Exec time: %llu; %llu", exit - enter, exit_mc - enter_mc);

    // Return
    return;   
}


Can anyone explain why there are such differences? I would expect some drift, maybe even up to a millisecond, but over 10 milliseconds? how is that possible?



I consider this to be a bug.

 

Here is a solution to the falsly behaving MQL5 function:

//+------------------------------------------------------------------+
//|                                                   Playground.mq5 |
//|             Copyright 2020, Freie Netze UG (haftunbgsbeschränkt) |
//|                                      https://www.freie-netze.de/ |
//+------------------------------------------------------------------+



//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
ulong _GetTickCount64()
{
    // Static init
    static ulong offset_ms = NULL;

    if(offset_ms != NULL)
    { return((GetMicrosecondCount() / 1000) + offset_ms); }

    // Local init
    ulong start_ms  = NULL;
    ulong start_mc  = NULL;
    ulong stop_ms   = 1;
    ulong stop_mc   = 1;

    while( (((stop_mc - start_mc) / 1000) != (stop_ms - start_ms))
        && (!_StopFlag) )
    {
        start_ms = GetTickCount64();
        start_mc = GetMicrosecondCount();
        while((start_mc + 250000) > GetMicrosecondCount());
        stop_mc = GetMicrosecondCount();
        stop_ms = GetTickCount64();
    }
    offset_ms = stop_ms - (stop_mc / 1000);

    // Return
    return((GetMicrosecondCount() / 1000) + offset_ms);
}
#define GetTickCount64 _GetTickCount64


//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    EventSetMillisecondTimer(1000);

    GetTickCount64();

    // Return
    return(INIT_SUCCEEDED);
}


//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    printf("%s", "OnInit()");

    // Return
    return;
}


//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{

    // Return
    return;   
}


//+------------------------------------------------------------------+
//| Expert timer function                                            |
//+------------------------------------------------------------------+
void OnTimer()
{
    printf("OnTimer()");
    ulong enter = GetTickCount64();
    ulong enter_mc = GetMicrosecondCount();


        ulong mics = GetMicrosecondCount();
        while(mics + 500000 > GetMicrosecondCount());
        //Sleep(500);
        mics = GetMicrosecondCount() - mics;


    ulong exit_mc = GetMicrosecondCount();
    ulong exit = GetTickCount64();

    printf("Exec time: %llu; %llu", exit - enter, exit_mc - enter_mc);
    
    #undef GetTickCount64
    printf("Sys: %llu; Own: %llu", GetTickCount64(), _GetTickCount64());

    // Return
    return;   
}



Proof:


 
Dominik Christian Egert:A simple attempt to synchronize the OnTimer() call to TimeLocal to be called at around the next second has revealed a question, I cannot wrap my head around.So I tried to analyze the behaviour and found that GetTickCount64() and GetMicrosecondCount() give confusing results, as can be seen in the screenshot. Here is the code to reproduce the output: Can anyone explain why there are such differences? I would expect some drift, maybe even up to a millisecond, but over 10 milliseconds? how is that possible?I consider this to be a bug.

It's in the documentation. The resolution or accuracy is only 10-16 millisecond.

Note

The counter is limited to the accuracy of the system timer, which usually returns a result with the 10-16 millisecond precision. Unlike GetTickCount, which is of uint type and the contents of which overflow every 49.7 days in the case of continued computer operation, GetTickCount64() can be used for the unlimited computer operation time and is not subject to overflow.

 

Now that you have posted, I remember reading that. - Still, as you can see, its not very hard to create a reliable function with consistent output.

I dont understand why this needs to be done by the user. - Well, its documented, therefore it seems not to be a bug, but a feature. 

Anyways, thank you for clarification.
 

And while we are at it, Sleep is as unreliable as GetTickCount64()...


Here would be a fix for that as well:

void _Sleep(const int milliseconds)
{
    // Local init
    const ulong stop = GetMicrosecondCount() + (milliseconds * 1000);

    // Sleep
    while( (stop > GetMicrosecondCount())
        && (!_StopFlag) );

    // Return
    return;
}
#define Sleep _Sleep


Giving this as result:



Although this function also works in indicators and a check for that would need insertion:


void _Sleep(const int milliseconds)
{
    // Environment check
    const static bool is_indicator = ((ENUM_PROGRAM_TYPE)MQLInfoInteger(MQL_PROGRAM_TYPE) == PROGRAM_INDICATOR);
    if(is_indicator)
    { return; }

    // Local init
    const ulong stop = GetMicrosecondCount() + (milliseconds * 1000);

    // Sleep
    while( (stop > GetMicrosecondCount())
        && (!_StopFlag) );

    // Return
    return;
}
#define Sleep _Sleep


Above code gives a fully valid drop-in replacement to the original function, although, I could imagine, there wont be a "nop" to the CPU and therefore the OS will not put the executing thread to sleep, but thats just a side-effect, as far as I am concerned.

 
Dominik Christian Egert #:

And while we are at it, Sleep is as unreliable as GetTickCount64()...


Here would be a fix for that as well:


Giving this as result:



Although this function also works in indicators and a check for that would need insertion:



Above code gives a fully valid drop-in replacement to the original function, although, I could imagine, there wont be a "nop" to the CPU and therefore the OS will not put the executing thread to sleep, but thats just a side-effect, as far as I am concerned.

In case it is relevant, there is a standard sleep function - I made the mistake of writing my own only to discover this later :)


https://www.mql5.com/en/docs/common/sleep

Documentation on MQL5: Common Functions / Sleep
Documentation on MQL5: Common Functions / Sleep
  • www.mql5.com
Sleep - Common Functions - MQL5 Reference - Reference on algorithmic/automated trading language for MetaTrader 5
 
R4tna C #:

In case it is relevant, there is a standard sleep function - I made the mistake of writing my own only to discover this later :)


https://www.mql5.com/en/docs/common/sleep

Thank you, but the issue is not the "standard" availability, but its flawed behaviour, especially if you try to synchornize your execution thread to RTC. It just doesnt do the job as shown in my previous post. - It hovers around with a blurr of over 50 milliseconds.

 
Dominik Christian Egert #:

Thank you, but the issue is not the "standard" availability, but its flawed behaviour, especially if you try to synchornize your execution thread to RTC. It just doesnt do the job as shown in my previous post. - It hovers around with a blurr of over 50 milliseconds.

Noted, thanks 

 
Dominik Christian Egert #:

Thank you, but the issue is not the "standard" availability, but its flawed behaviour, especially if you try to synchornize your execution thread to RTC. It just doesnt do the job as shown in my previous post. - It hovers around with a blurr of over 50 milliseconds.

Hello there , 

Sorry for being late at that, but here we go: The "problem" we are seeing on your post, i.e. sometimes you get 485ms sometimes 500, is not a flaw but rather a feature :) Joke aside , it's all due to the OS system clock, which in the case of Windows is usually 1/64sec - or 15.625ms, hence the difference we are seeing.. You can find some topics of the web of course, but here are a couple of quick links related to the subject : 

 https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep 

 https://vvvv.org/contribution/windows-system-timer-tool

greets  

Sleep function (synchapi.h) - Win32 apps
Sleep function (synchapi.h) - Win32 apps
  • 2021.10.13
  • karl-bridge-microsoft
  • docs.microsoft.com
Suspends the execution of the current thread until the time-out interval elapses.
 

The real question is why do you care? Why do you need a timer?
     How To Ask Questions The Smart Way. (2004)
          The XY Problem

Nothing has changed. Return from OnTick/OnCalculate and wait for a change.

 
William Roeder #:

The real question is why do you care? Why do you need a timer?
     How To Ask Questions The Smart Way. (2004)
          The XY Problem

Nothing has changed. Return from OnTick/OnCalculate and wait for a change.

Yes, I was wondering about the relevance of this too...

Reason: