Performance issue in struct initialization

Basile B. via Digitalmars-d digitalmars-d at puremagic.com
Mon Jun 20 13:34:12 PDT 2016


On Monday, 20 June 2016 at 11:45:28 UTC, Johannes Pfau wrote:
> Am Sun, 19 Jun 2016 20:52:52 +0000
> schrieb deadalnix <deadalnix at gmail.com>:
>
>> On Sunday, 19 June 2016 at 11:11:18 UTC, Basile B. wrote:
>> > On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei 
>> > Alexandrescu wrote:
>> >> https://issues.dlang.org/show_bug.cgi?id=15951. I showed a 
>> >> few obvious cases, but finding the best code in general is 
>> >> tricky. Ideas? -- Andrei
>> >
>> > A new "@noinit" attribute could solve this issue and other 
>> > cases where the initializer is a handicap:
>> >
>> > The runtime would skip the copy of the initializer when
>> > 1- @noinit is an attribute of an aggregate.
>> > 2- a ctor that takes at least one parameter is present.
>> > 3- the default ctor is disabled (only a condition for the
>> > structs or the new free form unions)
>> >
>> > // OK
>> > @noinit struct Foo
>> > {
>> >    uint a;
>> >    @disable this();
>> >    this(uint a){}
>> > }
>> >
>> > // not accepted because a ctor with parameters misses
>> > @noinit struct Foo
>> > {
>> >    @disable this();
>> > }
>> >
>> > // Ok but a warning will be emitted...
>> > @noinit struct Foo
>> > {
>> >    uint a = 1; // ...because this value is ignored
>> >    @disable this();
>> >    this(uint a){}
>> > }
>> >
>> > // not accepted because there's a default ctor
>> > @noinit struct Foo
>> > {
>> >    this(){}
>> > }
>> >
>> > The rationale is that when there's a constructor that takes 
>> > parameters it's really suposed to initialize the aggregate. 
>> > At least that would be the contract, the "postulate', put by 
>> > the usage of @noinit.
>> 
>> No new attribute please. Just enable the damn thing where 
>> there is an argumentless constructor and be done with it.
>
> Can somebody explain how exactly are constructors related to 
> the problem?

The initializer is copied to the chunk that represents the new 
aggregate instance. The idea here is to explicitly disable this 
copy to get a faster instantiation, under certain conditions. For 
example in allocator.make() this would mean "skip the call to 
emplace() and call directly __ctor() on the new chunk".

> If I've got this:
> struct Foo
> {
>     int a = 42;
>     int b = void;
>
>     @disable this();
>     this(int b)
>     {this.b = b;}
> }
> auto foo = Foo(41);
>
> I'd still expect a to be initialized to 42.

That's exactly why with @noinit you would get a warning

> Note that this does _not_ require a initializer symbol or 
> memcpy.

I'be verified again and the initializer is copied. For example 
with a gap in the static initial values:


struct Foo
{
      int a = 7;
      int gap = void;
      int c = 8;
      @disable this();
      this(int a)
      {this.a = a;}
}
auto fun(){ auto foo = Foo(41); return foo.a;}


I get (-O -release -inline) for fun():

0000000000457D58h  sub rsp, 18h
0000000000457D5Ch  mov esi, 004C9390h // 
typid(Foo).initializer.ptr
0000000000457D61h  lea rdi, qword ptr [rsp+08h]
0000000000457D66h  movsq //copy 8, note that the gap is not 
handled at all
0000000000457D68h  movsb //copy 1
0000000000457D69h  movsb //copy 1
0000000000457D6Ah  movsb //copy 1
0000000000457D6Bh  movsb //copy 1
0000000000457D6Ch  mov eax, 00000029h //inlined __ctor
0000000000457D71h  mov dword ptr [rsp+08h], eax
0000000000457D75h  add rsp, 18h
0000000000457D79h  ret

But that was obvious. How would you expect a = 7 and c = 8 to be 
generated otherwise ?

with @noinit you would get

sub rsp, 18h
mov eax, 00000029h //inlined __ctor
mov dword ptr [rsp+08h], eax
add rsp, 18h
ret

That's a really simple and pragmatic idea. But I guess that if 
you manage to get the compiler to generate a smarter initializer 
copy then the problem is fixed. At least I'll experiment this 
noinit stuff in my user library.


More information about the Digitalmars-d mailing list