Re: Cost of deleting a null pointer
On 15.12.2011 00:24, Le Chaud Lapin wrote:
On Nov 30, 10:29 pm, "A. McKenney"<alan_mckenn...@yahoo.com> wrote:
Recently, someone was doing some profiling of our code, and found that
code essentially like the following was taking far more time than
expected:
(...)
for ( int i = 0; i< 18; ++i )
{
delete tlist[i];
tlist[i] = 0;
}
}
In most cases, all the elements of tlist were null.
He found that if he replaced
delete tlist[i];
with
if ( tlist[i] ) delete tlist[i];
it sped the function up by about a factor of 30. His assumption was
that all the destructor and freeing logic was being gone through
even when the pointer was null.
(...)
I had assumed the same thing when I experienced something similar a
few years ago. See below.
Is this just a Quality of Implementation issue, or is it reasonable to
expect that deleting a null pointer should be expensive?
(...) so I went snooping around for
opportunities to optimize. To my surprise, the compiler was not
checking to see if the argument of delete() was 0 before invoking the
machinery that effects delete().
Was this type perchance living in a DLL? (I haven't have time to try that out like below.)
So I added a test, as you did above,
and the difference was dramatic. I do not remember if it was 30x, nor
if I was using Debug or Release code (on Visual Studio 2008) were the
Debug configuration might add some extraneous fat just before
invocation, but I do remember that the difference was so great that I
made a note to self: "Explicitly test for 0 on delete from now on." I
also recall, after a thorough examination of the dis-assembly and some
reflection, arriving at the conclusion that the compiler writer was
very-well cognizant of what s/he had done and deliberately designed it
that way.
Since the op failed to mention his compiler, but you mention VS2008, I can add some info wrt. to VS2005 (unfortunately, I do not have a newer version available atm.)
Here's the disassembly from VS2005 for deleteing some pointers:
(Release mode, optimization /Ox - but full-prog-opt disabled)
+ + + + +
cout << "Deleting pointers ...\n";
0040110E mov edx,dword ptr [__imp_std::cout (403044h)]
00401114 push offset ___xi_z+30h (40314Ch)
00401119 push edx
0040111A call std::operator<<<std::char_traits<char> > (401310h)
0040111F add esp,8
delete p1;
00401122 test ebp,ebp
00401124 je main+126h (401136h)
00401126 mov ecx,ebp
00401128 call ValueType::~ValueType (401540h)
0040112D push ebp
0040112E call operator delete (401732h)
00401133 add esp,4
delete p2;
00401136 test ebx,ebx
00401138 je main+13Ah (40114Ah)
0040113A mov ecx,ebx
0040113C call ValueType::~ValueType (401540h)
00401141 push ebx
00401142 call operator delete (401732h)
00401147 add esp,4
delete pb;
0040114A test esi,esi
0040114C je main+148h (401158h)
0040114E mov eax,dword ptr [esi]
00401150 mov edx,dword ptr [eax]
00401152 push 1
00401154 mov ecx,esi
00401156 call edx
delete pd;
00401158 test edi,edi
0040115A je main+15Ch (40116Ch)
0040115C mov eax,dword ptr [edi]
0040115E mov ecx,dword ptr [eax+4]
00401161 mov edx,dword ptr [ecx+edi]
00401164 mov eax,dword ptr [edx]
00401166 add ecx,edi
00401168 push 1
0040116A call eax
+ + + +
As can be seen at 401122, 401136, 40114A, 401158 the compiler does in fact check for NULL. (Adding in an `if(p)` will be optimized out by my compiler if I read the other asm correctly.)
The debug version is rather interesting:
+ + + +
delete p1;
004117B3 mov eax,dword ptr [ebp-14h]
004117B6 mov dword ptr [ebp-14Ch],eax
004117BC mov ecx,dword ptr [ebp-14Ch]
004117C2 mov dword ptr [ebp-158h],ecx
004117C8 cmp dword ptr [ebp-158h],0
004117CF je main+276h (4117E6h)
004117D1 push 1
004117D3 mov ecx,dword ptr [ebp-158h]
004117D9 call ValueType::`scalar deleting destructor' (41106Eh)
004117DE mov dword ptr [ebp-1CCh],eax
004117E4 jmp main+280h (4117F0h)
004117E6 mov dword ptr [ebp-1CCh],0
if(p2) {
004117F0 cmp dword ptr [ebp-20h],0
004117F4 je main+2C3h (411833h)
delete p2;
004117F6 mov eax,dword ptr [ebp-20h]
004117F9 mov dword ptr [ebp-134h],eax
004117FF mov ecx,dword ptr [ebp-134h]
00411805 mov dword ptr [ebp-140h],ecx
0041180B cmp dword ptr [ebp-140h],0
00411812 je main+2B9h (411829h)
00411814 push 1
00411816 mov ecx,dword ptr [ebp-140h]
0041181C call ValueType::`scalar deleting destructor' (41106Eh)
00411821 mov dword ptr [ebp-1CCh],eax
00411827 jmp main+2C3h (411833h)
00411829 mov dword ptr [ebp-1CCh],0
}
+ + + +
As you can see, the "prelude" takes 4 mov commands without the if.
If adding in the if in the debug version, the cmp is done immediately (and another cmp again for delete, since no-optimizations.)
Obviously, "tweaking" the debug version is not something one normally does. (But you mentioned that you cannot remember if it were relese or debug in your VS2008 case.)
To conclude:
VS2005 adds an explicit "if" check before calling the dtor (either normal or virtual) and the operator delete.
Is there any reason to expect any compiler to do differently?
cheers,
Martin
p.s.:
The test code:
//types.h
class ValueType {
public:
ValueType();
~ValueType();
};
class Base {
public:
virtual ~Base();
};
class Derived : virtual public Base {
public:
Derived();
virtual ~Derived();
};
//types.cpp
#include "stdafx.h"
#include "types.h"
using std::cout;
ValueType::ValueType() {
cout << __FUNCTION__ << "\n";
}
ValueType::~ValueType() {
cout << __FUNCTION__ << "\n";
}
Base::~Base() {
cout << __FUNCTION__ << "\n";
}
Derived::Derived() {
cout << __FUNCTION__ << "\n";
}
Derived::~Derived() {
cout << __FUNCTION__ << "\n";
}
//main.cpp
#include "stdafx.h"
#include "types.h"
int main()
{
using namespace std;
ValueType *p1, *p2;
Base *pb;
Derived *pd;
if(time(0) > 0) {
p1 = new ValueType();
p2 = NULL;
pb = new Derived();
pd = NULL;
} else {
p1 = NULL;
p2 = new ValueType();
pb = NULL;
pd = new Derived();
}
cout << "Deleting pointers ...\n";
delete p1;
delete p2;
delete pb;
delete pd;
return 0;
}
--
I'm here to learn, you know.
Just waiting for someone,
to jump from the shadows,
telling me why I'm wrong.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]