placement new overhead

Discussion in 'C++' started by Marc, Jan 29, 2011.

  1. Marc

    Marc Guest

    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?
     
    Marc, Jan 29, 2011
    #1
    1. Advertising

  2. Marc

    Öö Tiib Guest

    On Jan 29, 5:31 pm, Marc <> wrote:
    > 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.
     
    Öö Tiib, Jan 29, 2011
    #2
    1. Advertising

  3. Marc

    Marc Guest

    Öö Tiib wrote:

    > On Jan 29, 5:31 pm, Marc <> wrote:
    >> 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 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.

    >> 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,


    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.
     
    Marc, Jan 29, 2011
    #3
  4. Marc

    Andy Venikov Guest

    On 01/29/2011 01:55 PM, Marc wrote:
    > Öö Tiib wrote:
    >
    >> On Jan 29, 5:31 pm, Marc<> wrote:
    >>> 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 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.
    >
    >>> 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,

    >
    > 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.


    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.
     
    Andy Venikov, Feb 15, 2011
    #4
  5. Marc

    James Kanze Guest

    On Jan 29, 3:31 pm, Marc <> wrote:

    > 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.

    --
    James Kanze
     
    James Kanze, Feb 16, 2011
    #5
  6. Marc

    Martin B. Guest

    On 16.02.2011 11:00, James Kanze wrote:
    > On Jan 29, 3:31 pm, Marc<> wrote:
    >
    >> 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.
    > ...


    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
     
    Martin B., Feb 16, 2011
    #6
  7. Marc

    itaj sherman Guest

    On Feb 16, 2:03 pm, "Martin B." <> wrote:
    > 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
     
    itaj sherman, Feb 16, 2011
    #7
  8. Marc

    itaj sherman Guest

    On Feb 16, 5:38 pm, itaj sherman <> wrote:
    > On Feb 16, 2:03 pm, "Martin B." <> wrote:
    >
    >
    >
    > 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
     
    itaj sherman, Feb 16, 2011
    #8
  9. Marc

    itaj sherman Guest

    On Feb 16, 5:38 pm, itaj sherman <> wrote:
    > On Feb 16, 2:03 pm, "Martin B." <> wrote:
    >
    >
    >
    > 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
     
    itaj sherman, Feb 16, 2011
    #9
  10. Marc

    James Kanze Guest

    On Feb 16, 12:03 pm, "Martin B." <> wrote:
    > On 16.02.2011 11:00, James Kanze wrote:
    > > On Jan 29, 3:31 pm, Marc<> wrote:


    > >> 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.
    > > ...


    > 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.

    --
    James Kanze
     
    James Kanze, Feb 17, 2011
    #10
  11. Marc

    itaj sherman Guest

    On Feb 17, 2:16 pm, James Kanze <> wrote:
    > On Feb 16, 12:03 pm, "Martin B." <> wrote:
    >


    >
    > §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.
    >
    > --
    > James Kanze


    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
     
    itaj sherman, Feb 17, 2011
    #11
  12. Marc

    itaj sherman Guest

    On Feb 17, 3:12 pm, itaj sherman <> wrote:
    > On Feb 17, 2:16 pm, James Kanze <> wrote:
    >
    >


    >
    > 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
     
    itaj sherman, Feb 17, 2011
    #12
  13. Marc

    itaj sherman Guest

    On Feb 16, 6:00 pm, itaj sherman <> wrote:
    > On Feb 16, 5:38 pm, itaj sherman <> wrote:
    >
    > > On Feb 16, 2:03 pm, "Martin B." <> wrote:

    >
    > > 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:
    >


    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
     
    itaj sherman, Feb 17, 2011
    #13
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. gcc
    Replies:
    11
    Views:
    15,442
    David Dorward
    Jun 10, 2004
  2. Wenjie

    is it placement new?

    Wenjie, Jun 26, 2003, in forum: C++
    Replies:
    0
    Views:
    941
    Wenjie
    Jun 26, 2003
  3. lallous
    Replies:
    10
    Views:
    621
    Steve Dubak
    Mar 6, 2004
  4. Mark P
    Replies:
    6
    Views:
    840
    James Dennett
    Apr 27, 2005
  5. jacob navia
    Replies:
    9
    Views:
    632
    Ian Collins
    May 22, 2012
Loading...

Share This Page