digitalmars.D - Performance issue in struct initialization
- Andrei Alexandrescu (2/2) Apr 23 2016 https://issues.dlang.org/show_bug.cgi?id=15951. I showed a few obvious
- rikki cattermole (25/27) Apr 23 2016 That opEquals that is generated looks awfully big as well.
- Basile B (15/18) Apr 23 2016 In the first example you've forget to assign void:
- Andrei Alexandrescu (5/11) Apr 23 2016 Thanks. I'm not looking for 100% equivalence as much as defining a
- Basile B (7/25) Apr 23 2016 I haven't seen but this problem is actually well known:
- Johan Engelen (22/25) May 27 2016 What I gather from the discussions is that the current spec says
- Era Scarecrow (6/15) May 28 2016 If opEquals and opCmp are overridden then I don't see why voids
- Johannes Pfau (10/26) Jun 20 2016 I don't think this is a good solution. A '=void' field will always
- Basile B. (37/40) Jun 19 2016 A new "@noinit" attribute could solve this issue and other cases
- Basile B. (3/43) Jun 19 2016 If RCStrings are always allocated with allocators then we can
- deadalnix (3/43) Jun 19 2016 No new attribute please. Just enable the damn thing where there
- Basile B. (2/52) Jun 19 2016 we need hobo structs...
- Basile B. (3/53) Jun 19 2016 One , if not the one I like the best:
- Johannes Pfau (17/68) Jun 20 2016 Can somebody explain how exactly are constructors related to the
- Basile B. (46/114) Jun 20 2016 The initializer is copied to the chunk that represents the new
- Johannes Pfau (16/148) Jun 20 2016 I meant this does not have to use a symbol. Right now it does, but
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
Apr 23 2016
On 24/04/2016 1:37 AM, 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? -- AndreiThat opEquals that is generated looks awfully big as well. Also form what I've read you could get a speed boost if you lump the mov's together. E.g. movq <memory>, rax movq <memory>, rbx movq <memory>, rcx movq <memory>, rdx movq <memory>, r8 movq <memory>, r9 ... movq rax, <memory> movq rbx, <memory> movq rcx, <memory> movq rdx, <memory> movq r8, <memory> movq r9, <memory> In theory, in newer cpu's that should be fairly cheap compare to switching between. Of course that won't help anything in decreasing instruction count. Unfortunately I can't find anything backing this up so take it with a grain of salt, also rep mov might be just as good. Also there is a new series of CPU's coming out in a month or two, so who knows if it'll change assuming its valid now.
Apr 23 2016
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? -- AndreiIn the first example you've forget to assign void: http://d.godbolt.org/#compilers:!((compiler:gdc46,options:'-O3',source:'struct+RCStr(C)%0A%7B%0A++++enum+uint+maxSmall+%3D+64+/+C.sizeof+-+1%3B%0A++++C%5BmaxSmall%5D+small+%3D+void%3B%0A++++ubyte+smallLen+%3D+void%3B%0A%7D%0A%0Aauto+fun()+%7B%0A++RCStr!!char+result+%3D+void%3B%0A++return+result%3B%0A%7D%0A')),filterAsm:(commentOnly:!t,directives:!t,labels:!t),version:3 to get the real equivalent to the C++ version. The problem in the second example is that the full initializer is always copied from the static layout typeid(Stuff).initializer[0..$], despite of the fact that some of the members are not iitialized, while in C++ only members that are init are copied. The DMD code is shorter than the GDC one because it copies the initializer with `rep movsq` while GDC unrolls the `rep` with a bunch of `movq`, but in both cases the init is always fully copied. So the problem is the suboptimal copy of the initializer.
Apr 23 2016
On 4/23/16 10:10 AM, Basile B wrote:On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei Alexandrescu wrote:Thanks. I'm not looking for 100% equivalence as much as defining a proper baseline in the C++ version. Asking the user to use "= void" is not acceptable. Andreihttps://issues.dlang.org/show_bug.cgi?id=15951. I showed a few obvious cases, but finding the best code in general is tricky. Ideas? -- AndreiIn the first example you've forget to assign void: http://d.godbolt.org/#compilers:!((compiler:gdc46,options:'-O3',source:'struct+RCStr(C)%0A%7B%0A++++enum+uint+maxSmall+%3D+64+/+C.sizeof+-+1%3B%0A++++C%5BmaxSmall%5D+small+%3D+void%3B%0A++++ubyte+smallLen+%3D+void%3B%0A%7D%0A%0Aauto+fun()+%7B%0A++RCStr!!char+result+%3D+void%3B%0A++return+result%3B%0A%7D%0A')),filterAsm:(commentOnly:!t,directives:!t,labels:!t),version:3 to get the real equivalent to the C++ version.
Apr 23 2016
On Saturday, 23 April 2016 at 14:10:06 UTC, Basile B wrote:On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei Alexandrescu wrote:I haven't seen but this problem is actually well known: https://issues.dlang.org/show_bug.cgi?id=11817 https://issues.dlang.org/show_bug.cgi?id=11331 It really looks that this can only be solved by a compiler update. Even with a custom this() with params and a the default this() disabled the initializer is **always** copied.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? -- AndreiIn the first example you've forget to assign void: http://d.godbolt.org/#compilers:!((compiler:gdc46,options:'-O3',source:'struct+RCStr(C)%0A%7B%0A++++enum+uint+maxSmall+%3D+64+/+C.sizeof+-+1%3B%0A++++C%5BmaxSmall%5D+small+%3D+void%3B%0A++++ubyte+smallLen+%3D+void%3B%0A%7D%0A%0Aauto+fun()+%7B%0A++RCStr!!char+result+%3D+void%3B%0A++return+result%3B%0A%7D%0A')),filterAsm:(commentOnly:!t,directives:!t,labels:!t),version:3 to get the real equivalent to the C++ version. The problem in the second example is that the full initializer is always copied from the static layout typeid(Stuff).initializer[0..$], despite of the fact that some of the members are not iitialized, while in C++ only members that are init are copied. The DMD code is shorter than the GDC one because it copies the initializer with `rep movsq` while GDC unrolls the `rep` with a bunch of `movq`, but in both cases the init is always fully copied. So the problem is the suboptimal copy of the initializer.
Apr 23 2016
https://issues.dlang.org/show_bug.cgi?id=15951 https://issues.dlang.org/show_bug.cgi?id=11817 https://issues.dlang.org/show_bug.cgi?id=11331What I gather from the discussions is that the current spec says that: ``` struct S { char[100] arr = void; } S a; S b; assert(a == b); ``` That language guarantee prevents optimization of the initialization (in this case, the optimized result would be no initialization at all). So a breaking language spec change would be needed. Is this pursued by anyone? Perhaps only relax the spec when the struct S overrides opEquals ? (Once the optimization is allowed, I think it will be a fun project for me to implement it in LDC. But please keep the discussion clean by not discussing how a compiler should make use of this language change, how to implement it, etc. Thanks!) cheers, Johan
May 27 2016
On Friday, 27 May 2016 at 09:02:17 UTC, Johan Engelen wrote:That language guarantee prevents optimization of the initialization (in this case, the optimized result would be no initialization at all). So a breaking language spec change would be needed. Is this pursued by anyone? Perhaps only relax the spec when the struct S overrides opEquals ? (Once the optimization is allowed, I think it will be a fun project for me to implement it in LDC. But please keep the discussion clean by not discussing how a compiler should make use of this language change, how to implement it, etc. Thanks!)If opEquals and opCmp are overridden then I don't see why voids in initialization can't work since how you are comparing it would determine equality and not a bitwise compare... Hmmm had a longer reply involving changes to a new struct type with a few changes...
May 28 2016
Am Sat, 28 May 2016 07:08:52 +0000 schrieb Era Scarecrow <rtcvb32 yahoo.com>:On Friday, 27 May 2016 at 09:02:17 UTC, Johan Engelen wrote:I don't think this is a good solution. A '=void' field will always break 'a is b', even with a user defined opEquals. And of course a custom opEquals could do bit comparisons as well. I think a better way to frame this for the spec is: * A struct containing a =void field (directly or nested) cannot be compared bitwise. The compiler is allowed to fill the =void parts with arbitrary data. a == b will only compile if a user provided opEquals is available. a is b will never compile for such structs.That language guarantee prevents optimization of the initialization (in this case, the optimized result would be no initialization at all). So a breaking language spec change would be needed. Is this pursued by anyone? Perhaps only relax the spec when the struct S overrides opEquals ? (Once the optimization is allowed, I think it will be a fun project for me to implement it in LDC. But please keep the discussion clean by not discussing how a compiler should make use of this language change, how to implement it, etc. Thanks!)If opEquals and opCmp are overridden then I don't see why voids in initialization can't work since how you are comparing it would determine equality and not a bitwise compare...
Jun 20 2016
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? -- AndreiA 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.
Jun 19 2016
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:If RCStrings are always allocated with allocators then we can implement this at the library level...otherwise it's a DIP ;)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? -- AndreiA 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.
Jun 19 2016
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:No new attribute please. Just enable the damn thing where there is an argumentless constructor and be done with it.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? -- AndreiA 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.
Jun 19 2016
On Sunday, 19 June 2016 at 20:52:52 UTC, deadalnix wrote:On Sunday, 19 June 2016 at 11:11:18 UTC, Basile B. wrote:we need hobo structs...On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei Alexandrescu wrote:No new attribute please. Just enable the damn thing where there is an argumentless constructor and be done with it.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? -- AndreiA 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.
Jun 19 2016
On Sunday, 19 June 2016 at 20:52:52 UTC, deadalnix wrote:On Sunday, 19 June 2016 at 11:11:18 UTC, Basile B. wrote:One , if not the one I like the best: https://www.youtube.com/watch?v=xNRBajLM8_4On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei Alexandrescu wrote:No new attribute please. Just enable the damn thing where there is an argumentless constructor and be done with it.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? -- AndreiA 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.
Jun 19 2016
Am Sun, 19 Jun 2016 20:52:52 +0000 schrieb deadalnix <deadalnix gmail.com>:On Sunday, 19 June 2016 at 11:11:18 UTC, Basile B. wrote:Can somebody explain how exactly are constructors related to the problem? 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. Note that this does _not_ require a initializer symbol or memcpy. I'll post a more detailed follow up post to issue 15951.On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei Alexandrescu wrote:No new attribute please. Just enable the damn thing where there is an argumentless constructor and be done with it.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? -- AndreiA 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.
Jun 20 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 gmail.com>: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".On Sunday, 19 June 2016 at 11:11:18 UTC, Basile B. wrote:Can somebody explain how exactly are constructors related to the problem?On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei Alexandrescu wrote:No new attribute please. Just enable the damn thing where there is an argumentless constructor and be done with it.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? -- AndreiA 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.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 warningNote 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.
Jun 20 2016
Am Mon, 20 Jun 2016 20:34:12 +0000 schrieb Basile B. <b2.temp gmx.com>:On Monday, 20 June 2016 at 11:45:28 UTC, Johannes Pfau wrote:I meant this does not have to use a symbol. Right now it does, but that's an implementation issue.Am Sun, 19 Jun 2016 20:52:52 +0000 schrieb deadalnix <deadalnix gmail.com>: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".On Sunday, 19 June 2016 at 11:11:18 UTC, Basile B. wrote:Can somebody explain how exactly are constructors related to the problem?On Saturday, 23 April 2016 at 13:37:31 UTC, Andrei Alexandrescu wrote:No new attribute please. Just enable the damn thing where there is an argumentless constructor and be done with it.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? -- AndreiA 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.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 warningNote 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 ?Instead of doing foo = Foo.init(symbol) you could do foo = {a:7, c:8}(literal) (in the compiler). Then you don't need memory to memory moves at all if your architecure allows hardcoding constants in instructions. Additionally this allows the optimizer to see if you're writing to a default initialized variable and remove the useless initialization. If you don't have any default initializers / all are =void, foo = {} is a no-op. With noinit using default initializers causes a warning, with my idea using default initializers works fine and if you don't want any, just set all fields to =void. (This does not produce optimal code right now, but considering the =void fields when initializing is a simple change in GDC) See https://issues.dlang.org/show_bug.cgi?id=15951#c4 for details.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.
Jun 20 2016