July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | Dmitry Olshansky: > The problem is designing such classes and then documenting: "you should always use it as 'scope' ", is awkward. If you really want a class to be used as scope only you can do this, see the error message: scope class Foo {} void main() { Foo f = new Foo; } > The second writefln prints garbage. I guess it's because of pointer to the long gone stackframe, which is ovewritten by the first writeln. Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement. Bye, bearophile |
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to bearophile | On 21.07.2010 16:26, bearophile wrote: > Dmitry Olshansky: > >> The problem is designing such classes and then documenting: "you should >> always use it as 'scope' ", is awkward. >> > If you really want a class to be used as scope only you can do this, see the error message: > > scope class Foo {} > void main() { > Foo f = new Foo; > } > > > >> The second writefln prints garbage. I guess it's because of pointer to >> the long gone stackframe, which is ovewritten by the first writeln. >> > Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement. > > Bye, > bearophile > Going further with library implementation as opposed to language feature, I made a (somewhat) successful try at implementing scoped classes: struct Scoped(T){ ubyte[__traits(classInstanceSize, Test)] _payload; T getPayload(){ return cast(T)(_payload.ptr); } alias getPayload this; static Scoped opCall(Args...)(Args args) if ( is(typeof(T.init.__ctor(args))) ){// TODO: should also provide decent error message Scoped!T s; emplace!T(cast(void[])s._payload,args); return s; } ~this(){ clear(getPayload); } } now replace the orignal while loop with this: while (i < N) { auto testObject = Scoped!Test(i, i, i, i, i, i); //assuming we have aforementioned evil function func(Test t), that keeps global reference to t. //fun(testObject); //uncoment to get an compile error - type mismatch testObject.doSomething(i, i, i, i, i, i); testObject.doSomething(i, i, i, i, i, i); testObject.doSomething(i, i, i, i, i, i); testObject.doSomething(i, i, i, i, i, i); i++; } and all works just the same as with deprecated scope storage class. Even better it disallows passing the variable to functions expecting vanilla Test, it's limiting but for a good reason. There are still issues that should be solved (name clash for one, plus the ability to define default construct Scoped!T) but overall it's OK to me. -- Dmitry Olshansky |
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | Dmitry Olshansky wrote:
> On 21.07.2010 16:26, bearophile wrote:
>> Dmitry Olshansky:
>>
>>> The problem is designing such classes and then documenting: "you should
>>> always use it as 'scope' ", is awkward.
>>>
>> If you really want a class to be used as scope only you can do this, see the error message:
>>
>> scope class Foo {}
>> void main() {
>> Foo f = new Foo;
>> }
>>
>>
>>
>>> The second writefln prints garbage. I guess it's because of pointer to
>>> the long gone stackframe, which is ovewritten by the first writeln.
>>>
>> Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement.
>>
>> Bye,
>> bearophile
>>
> Going further with library implementation as opposed to language feature, I made a (somewhat) successful try at implementing scoped classes:
>
> struct Scoped(T){
> ubyte[__traits(classInstanceSize, Test)] _payload;
> T getPayload(){
> return cast(T)(_payload.ptr);
> }
> alias getPayload this;
> static Scoped opCall(Args...)(Args args) if ( is(typeof(T.init.__ctor(args))) ){// TODO: should also provide decent error message
> Scoped!T s;
> emplace!T(cast(void[])s._payload,args);
> return s;
> }
> ~this(){
> clear(getPayload);
> }
> }
>
> now replace the orignal while loop with this:
> while (i < N) {
> auto testObject = Scoped!Test(i, i, i, i, i, i);
> //assuming we have aforementioned evil function func(Test t), that keeps global reference to t.
> //fun(testObject); //uncoment to get an compile error - type mismatch
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> i++;
> }
>
> and all works just the same as with deprecated scope storage class.
> Even better it disallows passing the variable to functions expecting vanilla Test, it's limiting but for a good reason.
> There are still issues that should be solved (name clash for one, plus the ability to define default construct Scoped!T) but overall it's OK to me.
>
Nice work. To avoid name clashes with alias this, you may want to use a
trick invented by Shin Fujishiro:
struct ExWhyZee {
template ExWhyZee() {
// implementation goes here
}
alias ExWhyZee!().whatever this;
}
This way you never have a confusion between the symbols defined by the
wrapped type and your own type. I use a variant of the same trick in
RefCounted.
Andrei
|
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | Dmitry Olshansky wrote: > On 21.07.2010 16:26, bearophile wrote: >> Dmitry Olshansky: >> >>> The problem is designing such classes and then documenting: "you should >>> always use it as 'scope' ", is awkward. >>> >> If you really want a class to be used as scope only you can do this, see the error message: >> >> scope class Foo {} >> void main() { >> Foo f = new Foo; >> } >> >> >> >>> The second writefln prints garbage. I guess it's because of pointer to >>> the long gone stackframe, which is ovewritten by the first writeln. >>> >> Yes scope has this and other problems (and I think two of them can be fixed), but I don't think emplace() is a big improvement. >> >> Bye, >> bearophile >> > Going further with library implementation as opposed to language feature, I made a (somewhat) successful try at implementing scoped classes: I salute this approach. > struct Scoped(T){ > ubyte[__traits(classInstanceSize, Test)] _payload; s/Test/T/ I suppose. > T getPayload(){ > return cast(T)(_payload.ptr); > } > alias getPayload this; > static Scoped opCall(Args...)(Args args) if ( is(typeof(T.init.__ctor(args))) ){// TODO: should also provide decent error message > Scoped!T s; > emplace!T(cast(void[])s._payload,args); > return s; > } > ~this(){ > clear(getPayload); > } > } > > now replace the orignal while loop with this: > while (i < N) { > auto testObject = Scoped!Test(i, i, i, i, i, i); > //assuming we have aforementioned evil function func(Test t), that keeps global reference to t. > //fun(testObject); //uncoment to get an compile error - type mismatch > testObject.doSomething(i, i, i, i, i, i); > testObject.doSomething(i, i, i, i, i, i); > testObject.doSomething(i, i, i, i, i, i); > testObject.doSomething(i, i, i, i, i, i); > i++; > } > > and all works just the same as with deprecated scope storage class. > Even better it disallows passing the variable to functions expecting vanilla Test, it's limiting but for a good reason. > There are still issues that should be solved (name clash for one, plus the ability to define default construct Scoped!T) but overall it's OK to me. I agree Scope has a rightful place in the standard library. Andrei |
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | Dmitry Olshansky wrote:
> now replace the orignal while loop with this:
> while (i < N) {
> auto testObject = Scoped!Test(i, i, i, i, i, i);
> //assuming we have aforementioned evil function func(Test t),
> that keeps global reference to t.
> //fun(testObject); //uncoment to get an compile error - type
> mismatch
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> i++;
> }
With your code I `time` reports the below timings on my machine:
real 0m19.658s
user 0m19.590s
sys 0m0.010s
compared to:
real 0m9.122s
user 0m9.090s
sys 0m0.000s
with bearofiles original version. With -O -release its about 4 seconds faster for each.
|
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Rory Mcguire | On 07/21/2010 01:59 PM, Rory Mcguire wrote: > Dmitry Olshansky wrote: > >> now replace the orignal while loop with this: >> while (i< N) { >> auto testObject = Scoped!Test(i, i, i, i, i, i); >> //assuming we have aforementioned evil function func(Test t), >> that keeps global reference to t. >> //fun(testObject); //uncoment to get an compile error - type >> mismatch >> testObject.doSomething(i, i, i, i, i, i); >> testObject.doSomething(i, i, i, i, i, i); >> testObject.doSomething(i, i, i, i, i, i); >> testObject.doSomething(i, i, i, i, i, i); >> i++; >> } > > With your code I `time` reports the below timings on my machine: > real 0m19.658s > user 0m19.590s > sys 0m0.010s > > compared to: > real 0m9.122s > user 0m9.090s > sys 0m0.000s > > with bearofiles original version. With -O -release its about 4 seconds > faster for each. I compiled and ran the tests myself with -O -release -inline and got 1.95s for Dmitry's implementation and 1.55s for bearophile's. I optimized Dmitry's implementation in two ways: I replaced the call to clear() with a straight call to the destructor and added = void in two places to avoid double initialization. I got 1.11s, which significantly undercuts the implementation using scope. Here's the code I used for testing: struct Scoped(T) { ubyte[__traits(classInstanceSize, Test)] _payload = void; T getPayload(){ return cast(T)(_payload.ptr); } alias getPayload this; static Scoped opCall(Args...)(Args args) if (is(typeof(T.init.__ctor(args)))) { // TODO: should also provide decent error message Scoped!T s = void; emplace!T(cast(void[])s._payload,args); return s; } ~this() { static if (is(typeof(getPayload.__dtor()))) { getPayload.__dtor(); } } } final class Test { // 32 bytes each instance int i1, i2, i3, i4, i5, i6; this(int ii1, int ii2, int ii3, int ii4, int ii5, int ii6) { this.i1 = ii1; this.i2 = ii2; this.i3 = ii3; this.i4 = ii4; this.i5 = ii5; this.i6 = ii6; } void doSomething(int ii1, int ii2, int ii3, int ii4, int ii5, int ii6) { } } void main(string[] args) { enum int N = 10_000_000; int i; while (i < N) { auto testObject = Scoped!Test(i, i, i, i, i, i); //scope testObject = new Test(i, i, i, i, i, i); testObject.doSomething(i, i, i, i, i, i); testObject.doSomething(i, i, i, i, i, i); testObject.doSomething(i, i, i, i, i, i); testObject.doSomething(i, i, i, i, i, i); i++; } } Andrei |
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | Andrei Alexandrescu wrote:
> On 07/21/2010 01:59 PM, Rory Mcguire wrote:
>> Dmitry Olshansky wrote:
>>
>>> now replace the orignal while loop with this:
>>> while (i< N) {
>>> auto testObject = Scoped!Test(i, i, i, i, i, i);
>>> //assuming we have aforementioned evil function func(Test t),
>>> that keeps global reference to t.
>>> //fun(testObject); //uncoment to get an compile error - type
>>> mismatch
>>> testObject.doSomething(i, i, i, i, i, i);
>>> testObject.doSomething(i, i, i, i, i, i);
>>> testObject.doSomething(i, i, i, i, i, i);
>>> testObject.doSomething(i, i, i, i, i, i);
>>> i++;
>>> }
>>
>> With your code I `time` reports the below timings on my machine:
>> real 0m19.658s
>> user 0m19.590s
>> sys 0m0.010s
>>
>> compared to:
>> real 0m9.122s
>> user 0m9.090s
>> sys 0m0.000s
>>
>> with bearofiles original version. With -O -release its about 4 seconds faster for each.
>
> I compiled and ran the tests myself with -O -release -inline and got 1.95s for Dmitry's implementation and 1.55s for bearophile's.
>
> I optimized Dmitry's implementation in two ways: I replaced the call to clear() with a straight call to the destructor and added = void in two places to avoid double initialization. I got 1.11s, which significantly undercuts the implementation using scope.
>
> Here's the code I used for testing:
>
> struct Scoped(T) {
> ubyte[__traits(classInstanceSize, Test)] _payload = void;
> T getPayload(){
> return cast(T)(_payload.ptr);
> }
> alias getPayload this;
>
> static Scoped opCall(Args...)(Args args)
> if (is(typeof(T.init.__ctor(args)))) {
> // TODO: should also provide decent error message
> Scoped!T s = void;
> emplace!T(cast(void[])s._payload,args);
> return s;
> }
> ~this() {
> static if (is(typeof(getPayload.__dtor()))) {
> getPayload.__dtor();
> }
> }
> }
>
> final class Test { // 32 bytes each instance
> int i1, i2, i3, i4, i5, i6;
> this(int ii1, int ii2, int ii3, int ii4, int ii5, int ii6) {
> this.i1 = ii1;
> this.i2 = ii2;
> this.i3 = ii3;
> this.i4 = ii4;
> this.i5 = ii5;
> this.i6 = ii6;
> }
> void doSomething(int ii1, int ii2, int ii3, int ii4, int ii5, int
> ii6) {
> }
> }
>
> void main(string[] args)
> {
> enum int N = 10_000_000;
> int i;
> while (i < N) {
> auto testObject = Scoped!Test(i, i, i, i, i, i);
> //scope testObject = new Test(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> testObject.doSomething(i, i, i, i, i, i);
> i++;
> }
> }
>
>
> Andrei
Thanks Andrei!!!
|
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | Andrei Alexandrescu wrote:
> Here's the code I used for testing:
>
My timings with your code, I included the one without -inline because I havn't been using -inline in my other tests:
$ dmd -O -release -inline program1_2.d
$ time ./program1_2
real 0m0.526s
user 0m0.520s
sys 0m0.000s
$ dmd -O -release program1_2.d
$ time ./program1_2
real 0m0.820s
user 0m0.810s
sys 0m0.000s
and bearofiles with -inline:
$ time ./program1
real 0m2.267s
user 0m2.260s
sys 0m0.000s
Nice improvement...so is this in phobos or druntime yet? :D
|
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Rory Mcguire | Rory Mcguire wrote: > Andrei Alexandrescu wrote: [snip] > Thanks Andrei!!! Don't mention it. Thanks for not over-quoting in the future :o). Andrei |
July 21, 2010 Re: emplace, scope, enforce [Was: Re: Manual...] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | On 21.07.2010 23:16, Andrei Alexandrescu wrote: > I compiled and ran the tests myself with -O -release -inline and got 1.95s for Dmitry's implementation and 1.55s for bearophile's. > > I optimized Dmitry's implementation in two ways: I replaced the call to clear() with a straight call to the destructor and added = void in two places to avoid double initialization. I got 1.11s, which significantly undercuts the implementation using scope. > > Here's the code I used for testing: > > struct Scoped(T) { > ubyte[__traits(classInstanceSize, Test)] _payload = void; > T getPayload(){ > return cast(T)(_payload.ptr); > } > alias getPayload this; > > static Scoped opCall(Args...)(Args args) > if (is(typeof(T.init.__ctor(args)))) { > // TODO: should also provide decent error message > Scoped!T s = void; > emplace!T(cast(void[])s._payload,args); > return s; > } > ~this() { > static if (is(typeof(getPayload.__dtor()))) { > getPayload.__dtor(); > } > } > } Thanks for kind feedback (and showing some optimization tricks). Also this implementation still has issues with it: it calls dtor twice. Not a good trait for RAII technique ! :) Since it's now considered useful I feel myself obliged to enhance and correct it. Sadly enough I still haven't managed to apply an inner template trick. Here's the end result along with a simple unittest: struct Scoped(T){ ubyte[__traits(classInstanceSize, T)] _scopedPayload = void; T getScopedPayload(){ return cast(T)(_scopedPayload.ptr); } alias getScopedPayload this; this(Args...)(Args args){ static if (!is(typeof(getScopedPayload.__ctor(args)))) { static assert(false,"Scoped: wrong arguments passed to ctor"); }else { emplace!T(cast(void[])_scopedPayload,args); } } ~this() { static if (is(typeof(getScopedPayload.__dtor()))) { getScopedPayload.__dtor(); } } } //also track down destruction/construction class A{ this(int a){ writeln("A with ",a); } this(real r){ writeln("A with ",r); } ~this(){ writeln("A destroyed"); } } unittest{ { auto a = Scoped!A(42); } { auto b = Scoped!A(5.5); } } -- Dmitry Olshansky |
Copyright © 1999-2021 by the D Language Foundation