placement new overhead

M

Marc

Hello,

I have a memory region (I got it through std::aligned_storage), and I
initialize it using placement new. Looking at the generated code
(gcc-4.6), I notice that:
new(location) Type(arguments);
is implemented as:
if(location!=0) call the constructor

this causes quite a bit of overhead when Type is a trivial type (the
compiler can't always prove that location is not null).

I can't seem to find where the standard says that placement new can be
called with a null argument and should do nothing in that case. Is
this the case?
 
Ö

Öö Tiib

Hello,

I have a memory region (I got it through std::aligned_storage), and I
initialize it using placement new. Looking at the generated code
(gcc-4.6), I notice that:
new(location) Type(arguments);
is implemented as:
if(location!=0) call the constructor

this causes quite a bit of overhead when Type is a trivial type (the
compiler can't always prove that location is not null).

Can you provide some numbers? How many millions of placement news and
how large fraction of a second is that "quite a bit of overhead"?
I can't seem to find where the standard says that placement new can be
called with a null argument and should do nothing in that case. Is
this the case?

What implementation should do on case of null? It can't construct at
null pointer. If it is undefined, then implementation may do what it
pleases. Popular choices are ignoring (not checking), doing nothing,
throwing or raising a signal.
 
M

Marc

Öö Tiib said:
Can you provide some numbers? How many millions of placement news and
how large fraction of a second is that "quite a bit of overhead"?

I haven't measured, but in trivial cases, new takes twice as much time
as it should. And I have applications where this constructor accounts
for roughly 10% of the running time.

In any case, I am also interested in the theoretical aspect.
What implementation should do on case of null? It can't construct at
null pointer. If it is undefined,

That is what I am asking, whether it is undefined.
then implementation may do what it
pleases. Popular choices are ignoring (not checking), doing nothing,
throwing or raising a signal.

When I write *i=x, this is also undefined if i==0, but I don't see the
compiler generating a comparison. I would expect the same here.
 
A

Andy Venikov

I haven't measured, but in trivial cases, new takes twice as much time
as it should. And I have applications where this constructor accounts
for roughly 10% of the running time.

In any case, I am also interested in the theoretical aspect.


That is what I am asking, whether it is undefined.


When I write *i=x, this is also undefined if i==0, but I don't see the
compiler generating a comparison. I would expect the same here.

What's the optimization option you're using?

And I do think it unspecified (as opposed to undefined which would be
the case if you passed the actual NULL to the placement new).
Unspecified means that the compiler is free to do whatever it's pleases
without invoking UB.

Andy.
 
J

James Kanze

I have a memory region (I got it through std::aligned_storage), and I
initialize it using placement new. Looking at the generated code
(gcc-4.6), I notice that:
new(location) Type(arguments);
is implemented as:
if(location!=0) call the constructor
this causes quite a bit of overhead when Type is a trivial type (the
compiler can't always prove that location is not null).
I can't seem to find where the standard says that placement new can be
called with a null argument and should do nothing in that case. Is
this the case?

The signature of placement new is:
void* operator new( size_t, void* ) throw();
The "throw()" means that operator new can return a null pointer,
and that the generated code must behave correctly if it does.
(That's the general rule for all operator new functions: if they
have an empty exception specifier---they declare that they never
throw---then they report insufficient memory by returning a null
pointer.) From a language point of view, this means that the
compiler must test for null before calling the constructor.

With regards to this placement form of operator new in
particular, the standard doesn't say anything about the second
argument, *except* that it will be returned immediately. So
presumably, any pointer which would be valid as a return value
of this operator new would be a valid argument. And because of
the throw(), null is a valid return value.
 
M

Martin B.

The signature of placement new is:
void* operator new( size_t, void* ) throw();
The "throw()" means that operator new can return a null pointer,
and that the generated code must behave correctly if it does.
...

Interesting conclusion. Could you explain how you arrive at this
conclusion from the std?

I'll quote N3092 (p 448):
- - -
18.6.1.3 Placement forms
1 (...)
void* operator new(std::size_t size, void* ptr) throw();
2 Returns: ptr.
3 Remarks: Intentionally performs no other action.
4 [ Example: This can be useful for constructing an
object at a known address:
void* place = operator new(sizeof(Something));
Something* p = new (place) Something();
—end example ]
- - -

cheers,
Martin
 
I

itaj sherman

On 16.02.2011 11:00, James Kanze wrote:

Interesting conclusion. Could you explain how you arrive at this
conclusion from the std?

I'll quote N3092 (p 448):
- - -
18.6.1.3 Placement forms
1 (...)
void* operator new(std::size_t size, void* ptr) throw();
2 Returns: ptr.
3 Remarks: Intentionally performs no other action.
4 [ Example: This can be useful for constructing an
object at a known address:
void* place = operator new(sizeof(Something));
Something* p = new (place) Something();
—end example ]

I think I can fill in the details on that.

Consider an expression like:
new( a1, a2, a3 ) T( b1, b2, b3 );

In order for the above to compile, there needs to be a function
declared of the form:
void* operator new( std::size_t, U1, U2, U3 ) noexcept; //or without
noexcept.
That can match the call expression "operator new( a1, a2, a3 )".

That new expression has to call that allocation function and compare
the return value to null, in order to decide whether to construct an
object or not. Generally, there's no way for the new expression to
know apriori that the return value isn't null.

See 5.3.4/11:
The new-placement syntax is used to supply additional arguments to an
allocation function. If used, overload
resolution is performed on a function call created by assembling an
argument list consisting of the amount of
space requested (the first argument) and the expressions in the new-
placement part of the new-expression (thesecond and succeeding
arguments). The first of these arguments has type std::size_t and the
remaining arguments have the corresponding types of the expressions in
the new-placement.

See 5.4.3/13:
[ Note: unless an allocation function is declared with a non-throwing
exception-specification (15.4), it indicates
failure to allocate storage by throwing a std::bad_alloc exception
(Clause 15, 18.6.2.1); it returns a
non-null pointer otherwise. If the allocation function is declared
with a non-throwing exception-specification,
it returns null to indicate failure to allocate storage and a non-null
pointer otherwise. —end note ] If the
allocation function returns null, initialization shall not be done,
the deallocation function shall not be called,
and the value of the new-expression shall be null.


In our case: p is of type U*.
new( p ) T( b );

The standard says in 18.6.1.3 that there is a function pre-defined by
the implementation:
void* operator new( std::size_t, void* ptr ) noexcept
{
return ptr;
}
//it says it only returns ptr, and does nothing else.

It our case, the allocation function returns the value given to its
second parameter, originally from our variable p in the new
expression. But it must check whether it is null or not, and construct
an instance of T only if non-null.

There's no much way to avoid this pointer comparison, in this case,
that you want the object to be created in the address pointed to by p.
Even if you add your own allocation function to be called instead, the
new expression must compare its return value with null to decide
whether to construct.

Maybe if the optimizer could somehow prove that p is null, it would
ommit the comparision.

itaj
 
I

itaj sherman

Maybe if the optimizer could somehow prove that p is null, it would
ommit the comparision.

Ofcourse, I meant prove that it isn't null.

Appart from that, I should say, that as the OP, I too see that it can
cause a perfromance problem. The c++ performance attitude demands that
core operations like placement construction do not perform
unneccessary comparisons - that could be instead the responsibility of
the programmer to assure.

To further explain the problem, I explain how I think it could be
solved with a simple change in the standard:

-- one way --

In 18.6.1.3/3
The section about the pre-defined:
void* operator new( std::size_t, void* ) noexcept;
Add a remark that this function must not be called with a second
parameter null, and that would be undefined behaviour.

That implies that the above expression:
new( p ) T( b );
Has defined behaviour only if p is not null. Making it the
responsibility of the programmer to assure that or compare himself.
This means the compiler can always assume that p != NULL, and ommit
the comparison in this case.

-- another way --

If that first way is objected due to possibly breaking existing code,
it can be tweaked with a dummy parameter for that case:

pre defined enum:

namespace std
{
enum non_null_placement_t { non_null_placement };
}

and pre-defined allocation function:
void* operator new( std::size_t, void* ptr,
std::non_null_placement_t ) noexcept
{
return ptr;
}
with the requirement of ptr != NULL, otherwise undefined-behaviour.

And then:
new( p, std::non_null_placement ) T( b );
Has the required semantics.

itaj
 
I

itaj sherman

Consider an expression like:
new( a1, a2, a3 ) T( b1, b2, b3 );

In order for the above to compile, there needs to be a function
declared of the form:
void* operator new( std::size_t, U1, U2, U3 ) noexcept; //or without
noexcept.
That can match the call expression "operator new( a1, a2, a3 )".

That is "operator new( size, a1, a2, a3 )".
That should usually return a pointer to where the object should be
constructed.
That new expression has to call that allocation function and compare
the return value to null, in order to decide whether to construct an
object or not. Generally, there's no way for the new expression to
know apriori that the return value isn't null.

itaj
 
J

James Kanze

Interesting conclusion. Could you explain how you arrive at this
conclusion from the std?
I'll quote N3092 (p 448):
- - -
18.6.1.3 Placement forms
1 (...)
void* operator new(std::size_t size, void* ptr) throw();
2 Returns: ptr.
3 Remarks: Intentionally performs no other action.
4 [ Example: This can be useful for constructing an
object at a known address:
void* place = operator new(sizeof(Something));
Something* p = new (place) Something();
—end example ]
- - -

§3.7.3.1 (which specifies the requirements for allocator
functions), particularly §3.7.3.1/2: "If [the allocation
function] is successful, it shall return the address of the
start of a block of storage whose length in bytes shall be at
least as large as the requested size" and §3.7.3.1/3: "[...] If
an allocation function declared with an empty
exception-specification (15.4), throw(), fails to allocate
storage, it shall return a null pointer." Together, this
specifies clearly that the only legal return values of an
allocator function (and all "operator new" are allocator
functions) are a pointer to valid memory and a null pointer, and
the latter only if the allocator function has an empty exception
specifier.

Since "void* operator new(size_t size, void* ptr) throw()"
returns ptr directly, it stands to reason that ptr must meet the
requirements for a legal return value of this function. Without
the empty exception specification, that would mean a valid
pointer to the requested size; with the empty exception
specification, a null pointer is also valid.
 
I

itaj sherman

§3.7.3.1 (which specifies the requirements for allocator
functions), particularly §3.7.3.1/2: "If [the allocation
function] is successful, it shall return the address of the
start of a block of storage whose length in bytes shall be at
least as large as the requested size" and §3.7.3.1/3: "[...] If
an allocation function declared with an empty
exception-specification (15.4), throw(), fails to allocate
storage, it shall return a null pointer." Together, this
specifies clearly that the only legal return values of an
allocator function (and all "operator new" are allocator
functions) are a pointer to valid memory and a null pointer, and
the latter only if the allocator function has an empty exception
specifier.

Since "void* operator new(size_t size, void* ptr) throw()"
returns ptr directly, it stands to reason that ptr must meet the
requirements for a legal return value of this function. Without
the empty exception specification, that would mean a valid
pointer to the requested size; with the empty exception
specification, a null pointer is also valid.

By 5.4.3/13, I thought such allocator was allowed to either throw
bad_except or return null. It doesn't say that, but it is confusing.
I did not notice 3.7.3.1/3:
If an allocation function declared with a non-throwing exception-
specification (15.4) fails to allocate storage, it shall return a null
pointer. Any other allocation function that fails to allocate storage
shall indicate failure only by throwing an exception of a type that
would match a handler (15.3) of type std::bad_alloc (18.6.2.1).

In that case, maybe it can currently be solved with the optimizer?

How about doing ourselves what I suggested in my other post:

enum non_null_placement_t { non_null_placement };

// non-noexcept allocator
void* operator new( std::size_t, void* ptr,
std::non_null_placement_t )
{
return ptr;
}

And then:
new( p, std::non_null_placement ) T( b );
Has the required semantics.

Maybe in this case, the optimizer can assume that the allocator
doesn't return null (because without noexcept), and ommit the
condition.
And it can check its code and see that it neither throws an exception
(if it matters at all).

itaj
 
I

itaj sherman

How about doing ourselves what I suggested in my other post:

enum non_null_placement_t { non_null_placement };

// non-noexcept allocator
void* operator new( std::size_t, void* ptr,
std::non_null_placement_t )
{
        return ptr;

}
::non_null_placement_t


And then:
new( p, std::non_null_placement ) T( b );
Has the required semantics.

new( p, non_null_placement ) T( b );

itaj
 
I

itaj sherman

Ofcourse, I meant prove that it isn't null.

Appart from that, I should say, that as the OP, I too see that it can
cause a perfromance problem. The c++ performance attitude demands that
core operations like placement construction do not perform
unneccessary comparisons - that could be instead the responsibility of
the programmer to assure.

To further explain the problem, I explain how I think it could be
solved with a simple change in the standard:

As Kanze pointed out in his reply: An allocator function without
noexcept is not allowed to return null (instead it could throw
std::bad_alloc).

If at all, using that would produce the same results as my hipothetic
change (i.e. this change would improve nothing).

itaj
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,528
Members
45,000
Latest member
MurrayKeync

Latest Threads

Top