check pointer by "!" - page 6

 
Vladislav Boyko #:

Yes, || looks more attractive

As Alain said, its the same. Just use CheckPointer(), if you feel uncomfortable with !ptr.

But its the same, actually. (At least as far as current knowledge goes)
 
Vladislav Boyko #:

Yes, || looks more attractive

See, the point is:

true || false

At the left statement, the evaluation is done, short circuit.

false && true

Same.

Its different in these cases:

false || true

And:

true && false

Here both require the second operand to be evaluated.
 
Alain Verleyen #:
It's exactly the same. 

Yes, you are right.

Vladislav Boyko #:
Yes, || looks more attractive

It's probably because I'm so used to it😁

 
Dominik Egert #:
I often use "empty objects" to just run through the code, and avoid branching. This can make code run significantly faster.

If you are interested in this topic, here a video:

Thank you very much!

Dominik Egert #:
As Alain said, its the same. Just use CheckPointer(), if you feel uncomfortable with !ptr.

But its the same, actually. (At least as far as current knowledge goes)

I'm used to declaring small functions and immediately looking for a reason to exit them, so I mistakenly thought the conditions were different (because of habit, I have a hard time perceiving conditions that lead to additional nesting; I'm a fan of the return statement😁).

 
Alain Verleyen #:

So you are suggesting to not use the if shortcut ?

I understand the concept behind what you said but I can hardly believe it's useful in practice (about if shortcut like here).

Though I would be happy to learn I am wrong. Are you able to demonstrate the impact ? (not necessarily with pointer==NULL, I don't care).


I actually would suggest to (depending on the call frequency) use a dummy object to just burst through the code without branching at all, if possible.
 

It appears that, as expected, comparing with NULL is significantly faster than CheckPointer().

#define _size int(1e7)

class A
  {
public:
   int getId() const { return id; }
   
   static int getInstancesCount() { return instancesCount; }
   
private:
   static int instancesCount;
   const int  id;
public:
   A(int a_id) : id(a_id) { instancesCount++; }
  };

int A::instancesCount = 0;

class CCollection
  {
public:
   void benchmarkNull()
     {
      const ulong start = GetMicrosecondCount();
      long sum = 0;
      for(int i = 0; i < _size; i++)
         if(units[i] != NULL)
            sum += units[i].getId() % 1000;
      const ulong usElapsed = GetMicrosecondCount() - start;
      Print(__FUNCTION__":         ", usElapsed, " µs, sum = ", sum);
     }
   
   void benchmarkCheckPointer()
     {
      const ulong start = GetMicrosecondCount();
      long sum = 0;
      for(int i = 0; i < _size; i++)
         if(CheckPointer(units[i]) != POINTER_INVALID)
            sum += units[i].getId() % 1000;
      const ulong usElapsed = GetMicrosecondCount() - start;
      Print(__FUNCTION__": ", usElapsed, " µs, sum = ", sum);
     }
   
private:
   A* units[_size];
public:
   CCollection()
     {
      for(int i = 0; i < _size; i++)
         units[i] = NULL;
      for(int i = 0; i < _size; i += 500000)
        {
         const int idx = i + MathRand();
         units[idx] = new A(idx);
        }
      //Print("Valid pointers: ", A::getInstancesCount(), "/"string(_size));
     }
   
   ~CCollection()
     {
      for(int i = 0; i < _size; i++)
         if(CheckPointer(units[i]) == POINTER_DYNAMIC)
            delete units[i];
     }
  };

void OnStart()
  {
   CCollection* collection = new CCollection();
   collection.benchmarkNull();
   collection.benchmarkCheckPointer();
   delete collection;
  }
 
Vladislav Boyko #:

It appears that, as expected, comparing with NULL is significantly faster than CheckPointer().

Thanks. As this topic is about "!" operator, I modified your code to check it.

2025.09.30 07:54:03.239    CCollection::benchmarkNot:                    16126 µs, sum = 306697
2025.09.30 07:54:03.242    CCollection::benchmarkNullOrCheck:         2595 µs, sum = 306697
2025.09.30 07:55:01.113    CCollection::benchmarkNullOrCheck:         2436 µs, sum = 9136
2025.09.30 07:55:01.126    CCollection::benchmarkNot:                    13135 µs, sum = 9136
2025.09.30 07:55:07.418    CCollection::benchmarkNullOrCheck:         2463 µs, sum = 12135
2025.09.30 07:55:07.434    CCollection::benchmarkNot:                    16103 µs, sum = 12135
2025.09.30 07:55:20.724    CCollection::benchmarkNot:                    16399 µs, sum = 8854
2025.09.30 07:55:20.726    CCollection::benchmarkNullOrCheck:         2418 µs, sum = 8854

class CCollection
 {
public:
  void               benchmarkNot()
   {
    const ulong start = GetMicrosecondCount();
    long sum = 0;
    for(int i = 0; i < _size; i++)
     {
      if(!units[i])
        continue;
      sum += units[i].getId() % 1000;
     }
    const ulong usElapsed = GetMicrosecondCount() - start;
    Print(__FUNCTION__":         ", usElapsed, " µs, sum = ", sum);
   }

  void               benchmarkNullOrCheck()
   {
    const ulong start = GetMicrosecondCount();
    long sum = 0;
    for(int i = 0; i < _size; i++)
     {
      if(units[i]==NULL || CheckPointer(units[i])==POINTER_INVALID)
        continue;
      sum += units[i].getId() % 1000;
     }
    const ulong usElapsed = GetMicrosecondCount() - start;
    Print(__FUNCTION__":         ", usElapsed, " µs, sum = ", sum);
   }
private:
  A*                 units[_size];
public:
                     CCollection()
   {
    for(int i = 0; i < _size; i++)
      units[i] = NULL;
    for(int i = 0; i < _size; i += 500000)
     {
      const int idx = i + MathRand();
      units[idx] = new A(idx);
     }
    for(int i = 0; i < _size; i += MathRand())
     {
      if(units[i]==NULL)
        units[i] = new A(i);
      delete units[i];
     }
   }

                    ~CCollection()
   {
    for(int i = 0; i < _size; i++)
      if(CheckPointer(units[i]) == POINTER_DYNAMIC)
        delete units[i];
   }
 };

void OnStart()
 {
  CCollection* collection = new CCollection();
  collection.benchmarkNot();
  collection.benchmarkNullOrCheck();
  delete collection;
 }
 

With another view of coding:




#define _size int(1e7)

class A
{
    public:
    const long getId() const { return id; }
    
    static int getInstancesCount() { return instancesCount; }
    
    static int instancesCount;
    const int  id;

    public:
    A(int a_id) : id(a_id) { instancesCount++; }
};

int A::instancesCount = 0;


class CCollection
{
    public:
    void               benchmarkNot()
    {
        const ulong start = GetMicrosecondCount();
        long sum = 0;
        long count_null = NULL;
        for(int i = 0; i < _size; i++)
        {
            if(!units[i])
            { 
                count_null++;
                continue;
            }
            sum += units[i].getId() % 1000;
        }
        const ulong usElapsed = GetMicrosecondCount() - start;
        printf("%-45s %llu µs, sum = %lli; Null-Values: %lli; InstancesCount: %i", __FUNCTION__, usElapsed, sum, count_null, A::instancesCount);
    }
    
    void               benchmarkNot_DoubleCheck()
    {
        const ulong start = GetMicrosecondCount();
        long sum = 0;
        long count_null = NULL;
        for(int i = 0; i < _size; i++)
        {
            count_null += (!units[i]);
            sum += (!units[i]) ? NULL : (units[i].getId() % 1000);
        }
        const ulong usElapsed = GetMicrosecondCount() - start;
        printf("%-45s %llu µs, sum = %lli; Null-Values: %lli; InstancesCount: %i", __FUNCTION__, usElapsed, sum, count_null, A::instancesCount);
    }
    
    void               benchmarkNullOrCheck()
    {
        const ulong start = GetMicrosecondCount();
        long sum = 0;
        long count_null = NULL;
        for(int i = 0; i < _size; i++)
        {
            if(units[i]==NULL || CheckPointer(units[i])==POINTER_INVALID)
            {
                count_null++;
                continue;
            }
            sum += units[i].getId() % 1000;
        }
        const ulong usElapsed = GetMicrosecondCount() - start;
        printf("%-45s %llu µs, sum = %lli; Null-Values: %lli; InstancesCount: %i", __FUNCTION__, usElapsed, sum, count_null, A::instancesCount);
    }


    void               benchmarkNullOrCheck_NoBranch()
    {
        const ulong start = GetMicrosecondCount();
        long sum = 0;
        long count_null = NULL;
        for(int i = 0; i < _size; i++)
        {
            count_null += (units[i]==NULL || CheckPointer(units[i])==POINTER_INVALID);
            sum += (units[i]!=NULL && CheckPointer(units[i])!=POINTER_INVALID) ? (units[i].getId() % 1000) : NULL;
        }
        const ulong usElapsed = GetMicrosecondCount() - start;
        printf("%-45s %llu µs, sum = %lli; Null-Values: %lli; InstancesCount: %i", __FUNCTION__, usElapsed, sum, count_null, A::instancesCount);
    }


    private:
    A*                 units[_size];

    public:
    CCollection()
    {
        MathSrand(GetTickCount());
        for(int idx = 0; idx < _size; idx++)
        { units[idx] = ((MathRand() % 0x0F) == NULL) ? new A(idx) : NULL; }
    }
    
    ~CCollection()
    {
        for(int i = 0; i < _size; i++)
        if(CheckPointer(units[i]) == POINTER_DYNAMIC)
        delete units[i];
    }
};



void OnStart()
{
    CCollection* collection = new CCollection();
    collection.benchmarkNot();
    collection.benchmarkNot_DoubleCheck();
    collection.benchmarkNullOrCheck();
    collection.benchmarkNullOrCheck_NoBranch();

    delete collection;
}


 
Dominik Egert #:

With another view of coding:

Thanks for sharing a concrete example of the "no branch" paradigm.

As expected it's not better in this case 😮‍💨

What's the point of the benchmarkNot_DoubleCheck() ?

 
Alain Verleyen #:

As expected it's not better in this case 😮‍💨

Though, with a little improvement :

2025.09.30 09:18:48.828    CCollection::benchmarkNot                                 23914 µs, sum = 333795028; Null-Values: 9332204; InstancesCount: 667796
2025.09.30 09:18:48.858    CCollection::benchmarkNot_DoubleCheck            30039 µs, sum = 333795028; Null-Values: 9332204; InstancesCount: 667796
2025.09.30 09:18:48.868    CCollection::benchmarkNullOrCheck                     9694 µs, sum = 333795028; Null-Values: 9332204; InstancesCount: 667796
2025.09.30 09:18:48.879    CCollection::benchmarkNullOrCheck_NoBranch    11098 µs, sum = 333795028; Null-Values: 9332204; InstancesCount: 667796
2025.09.30 09:18:48.888    CCollection::benchmarkNullOrCheck_NoBranchAV  9201 µs, sum = 333795028; Null-Values: 9332204; InstancesCount: 667796

    void               benchmarkNullOrCheck_NoBranchAV()
    {
        const ulong start = GetMicrosecondCount();
        long sum = 0;
        long count_null = NULL;
        for(int i = 0; i < _size; i++)
        {
            bool invalid = (units[i]==NULL || CheckPointer(units[i])==POINTER_INVALID);
            count_null  += invalid;
            sum         += invalid ? 0 : (units[i].getId() % 1000);
        }
        const ulong usElapsed = GetMicrosecondCount() - start;
        printf("%-45s %llu µs, sum = %lli; Null-Values: %lli; InstancesCount: %i", __FUNCTION__, usElapsed, sum, count_null, A::instancesCount);
    }

PS: Result obtained with AVX512 maximum optimizations.

PS2: There is not really an improvement if you can have dangling pointer (pointer not NULL but invalid).