Object access by reference is 3 times faster than acess by ponter, why?

 

Always thought that:

SomeFunc(SomeObject &object){ //some operations with object..// }

// ..and //

SomeFunc(SomeObject *object){ //some operations with object..// }
literally means the same for compiler, or at least would be exexuted for the same time.


During optimisation of my very big script I've made a surprising discovery: accessing object members by reference is much faster than accessing them by the pointer.

Not a big issue for small scripts, but when you are waiting for two days for optimization to finish - one day instead is a good alternative)

Can anyone tell me what is the difference and what is going on inside of MQL5 for this two options?


Here's a very fast test script:

#property   strict
const int g_intesivity = 1000000000;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CTester
 {
public:
                     CTester() {val = 1;}
  ulong              getVal() {return val;}
protected:
  ulong              val;
 };
//+------------------------------------------------------------------+
//+ rand() is used to cheat compiler optimisations
//+------------------------------------------------------------------+
void OnStart()
 {
  Print("--------------");


//-direct access test:

  CTester t1();
  ulong timer1 = GetTickCount();

  srand(1); //-reset random generator to get always same sequence
  ulong result = 0;
  for(int i = 0; i < g_intesivity; i++)
   {
    result += t1.getVal() + rand();
   }

  timer1 = GetTickCount() - timer1;
  Print("direct access: ", timer1, " ms");


//-reference access test:

  CTester t2();
  srand(1); //-reset random generator to get always same sequence
  ulong timer2 = GetTickCount();
  const ulong res2 = IntenseMemberAccess(t2, g_intesivity);
  timer2 = GetTickCount() - timer2;
  Print("access by reference: ", timer2, " ms");


//-pointer access test:

  CTester *t3 = GetPointer(t2);
  ulong timer3 = GetTickCount();
  const ulong res3 = IntenseMemberAccess(t3, g_intesivity);
  timer3 = GetTickCount() - timer3;
  Print("access by pointer: ", timer3, " ms");


//-final results:

  if(res2 != res3 || result != res2)
   {
    Print("something went wrong:", res2, " ", res3);
   }
  else
   {
    Print("access by reference is ~", DoubleToString((double)timer3 / timer2, 2), " times faster than access by pointer");
   }

 }
//+------------------------------------------------------------------+
ulong IntenseMemberAccess(CTester &some_obj, const int intesivity)
 {
  srand(1); //-reset random generator to get always same sequence

  ulong result = 0;
  for(int i = 0; i < intesivity; i++)
   {
    result += some_obj.getVal() + rand();
   }
  return result;
 }
//+------------------------------------------------------------------+
ulong IntenseMemberAccess(CTester *some_obj, const int intesivity)
 {
  srand(1); //-reset random generator to get always same sequence

  ulong result = 0;
  for(int i = 0; i < intesivity; i++)
   {
    result += some_obj.getVal() + rand();
   }
  return result;
 }
//+------------------------------------------------------------------+

Sample output:

2021.03.13 20:23:34.948 test (GBPUSD,M10)       --------------
2021.03.13 20:23:35.961 test (GBPUSD,M10)       direct access: 1016 ms
2021.03.13 20:23:36.963 test (GBPUSD,M10)       access by reference: 1000 ms
2021.03.13 20:23:39.927 test (GBPUSD,M10)       access by pointer: 2968 ms
2021.03.13 20:23:39.927 test (GBPUSD,M10)       access by reference is ~2.97 times faster than access by pointer
 

Because the pointer has to be dereferenced (check Google about it if you don't know).

What really surprised me is the compiler is not able to optimize such code.

ulong IntenseMemberAccessOpt(CTester *some_obj, const int intesivity)
  {
   srand(1); //-reset random generator to get always same sequence

   ulong value  = some_obj.getVal();
   ulong result = 0;
   for(int i = 0; i < intesivity; i++)
     {
      result += value + rand();
     }
   return result;
  }

2021.03.13 22:14:36.986    364839 (EURCAD,M30)    direct access: 1078 ms
2021.03.13 22:14:38.063    364839 (EURCAD,M30)    access by reference: 1078 ms
2021.03.13 22:14:40.852    364839 (EURCAD,M30)    access by pointer: 2797 ms
2021.03.13 22:14:41.861    364839 (EURCAD,M30)    access by pointer optimized: 1000 ms
2021.03.13 22:14:41.861    364839 (EURCAD,M30)    access by reference is ~2.59 times faster than access by pointer

Files:
364839.mq5  4 kb
 

Because the pointer has to be dereferenced (check Google about it if you don't know).


Are you trying to provide some link here? It leads to something not related, thanks

 
Anyway thanks Alain for your answer. I've checked what does “dereferencing” a pointer mean, but I'm not sure how these applies to MQL5 as it's pointers are not a real pointers to memory but a descriptors of some kind.

Or I didn't get your point. Would you be so kind to edit my code so the pointer gets dereferenced. Maybe that would help me. Thank you so much.
 
terminalstat:
Anyway thanks Alain for your answer. I've checked what does “dereferencing” a pointer mean, but I'm not sure how these applies to MQL5 as it's pointers are not a real pointers to memory but a descriptors of some kind.

Or I didn't get your point. Would you be so kind to edit my code so the pointer gets dereferenced. Maybe that would help me. Thank you so much.

I didn't provide a link, there are enough on the net already. This green link is one of the wonderful useless feature of this site.

Pointers in MQL are not real pointers but the principle of dereferencing is the same. You provide an handle or a descriptor or whatever you name it and it has to be dereferenced to get the correct address of the data to be called, it's done a run time. With a reference it can be done at compile time.

 

Ok, got it. I thought by dereferencing you meant some additional manipulations to pointer I have to apply in my code. That confused me. But ok, finally get the idea.

Last question: why if I remove last function completely from the code:

ulong IntenseMemberAccessOpt(CTester *some_obj, const int intesivity)
  {
   srand(1); //-reset random generator to get always same sequence

   ulong value  = some_obj.getVal();
   ulong result = 0;
   for(int i = 0; i < intesivity; i++)
     {
      result += value + rand();
     }
   return result;
  }

it still compiles and run succesfully with almost identical results. Ain't it mandatory to have special function to accept pointers as arguments?