digitalmars.D.announce - automem v0.0.7 - C++ style smart pointers using
- Atila Neves (35/35) Apr 09 2017 Using std.experimental.allocator? Tired of writing `scope(exit)
- Martin Nowak (5/12) Apr 09 2017 Nice!
- Atila Neves (5/17) Apr 09 2017 I did not. Thanks for telling me!
- Martin Nowak (6/9) Apr 11 2017 I think the other design is slightly more correct, having a
- Atila Neves (6/16) Apr 11 2017 Unfortunately I later remembered that because it has a destructor
- Andrei Alexandrescu (11/42) Apr 09 2017 Nice!
- rikki cattermole (7/12) Apr 09 2017 To further this, I should rewrite my managed memory concept, it would be...
- Atila Neves (13/59) Apr 10 2017 I was aware of this, but here we have a crucial workaround -
- Stanislav Blinov (23/32) Apr 12 2017 Syntax is not the core of the issue, it's not about just marking
- Kagamin (14/25) Apr 17 2017 If we can control memory layout, we can do what shared_ptr does
- Stanislav Blinov (5/17) Apr 17 2017 I'm not sure I follow your comment. Indeed, that is how
- Martin Nowak (28/39) Apr 11 2017 Just repeating an older argument of mine which didn't make it
- Nicholas Wilson (5/13) Apr 11 2017 In LDC we have an attribute for that `allocSize`
- Martin Nowak (3/7) Apr 11 2017 Nice, if pure required strong purity, it would be quite a huge
- Basile B. (12/15) Apr 09 2017 I think that the Array misses
- Atila Neves (4/25) Apr 10 2017 Fixed now, thanks.
- Martin Nowak (3/8) Apr 11 2017 So the difference between std.container.Array and UniqueArray is
- Atila Neves (4/12) Apr 11 2017 That's the general idea, but I confess I didn't even look at
- Atila Neves (7/28) Apr 28 2017 dup is done. I'm trying to figure out how one would use .idup.
- mogu (4/7) Apr 09 2017 Nice!
- Atila Neves (4/13) Apr 10 2017 I started like that, but after many a `static if` realised they
- =?UTF-8?B?Tm9yZGzDtnc=?= (11/12) Apr 11 2017 You might find my own containers interesting, especially
Using std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: http://code.dlang.org/packages/automem Example: I think the code in the README should be enough to understand what's going on. Alpha stuff here but I think the main things missing are weak pointers and a ref counted array. Given that I've never had to use std::weak_ptr in C++, I'm not in a hurry to implement the former. Notable design decisions / features: . The smart pointers are responsible for allocating the memory for the container object using the allocator of choice. This is to guarantee that one can't allocate and deallocate using different allocators. . The allocator has to be specified as part of the type: this means the user can choose how to store it in the smart pointer, which for singletons (e.g. Mallocator) or stateless allocators means they can take up zero space. If a singleton (or the default theAllocator), the allocator doesn't need to be passed in to the constructor, otherwise it does. Specifying, e.g. Mallocator also means the relevant code can be marked nogc. . RefCounted only increments/decrements the ref count atomically if the contained type is `shared` . RefCounted!(shared T) can be sent to other threads. . UniqueArray behaves nearly like a normal array. You can even append to it, but it won't use GC memory (unless, of course, you chose to use GCAllocator)! I benchmarked RefCounted against C++'s std::shared_ptr comparing ldc to clang using both shared and non-shared payloads in D. std::shared_ptr is faster (I've never written a smart pointer before), but the advantage of non-atomic operations makes my D implementation just a bit faster when non-shared. I think with some work it can be significantly faster without any loss of thread safety. Atila
Apr 09 2017
On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:I benchmarked RefCounted against C++'s std::shared_ptr comparing ldc to clang using both shared and non-shared payloads in D. std::shared_ptr is faster (I've never written a smart pointer before), but the advantage of non-atomic operations makes my D implementation just a bit faster when non-shared. I think with some work it can be significantly faster without any loss of thread safety.Nice! You know you can overload `this(this) shared` do you? My plan was to use that for atomic RC, so that ppl. can use `shared(RefCounted)` when necessary.
Apr 09 2017
On Sunday, 9 April 2017 at 09:36:53 UTC, Martin Nowak wrote:On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:I did not. Thanks for telling me! The way I wrote it RefCounted!(shared T) works - RefCounted doesn't have to be shared itself, but I guess it could be. AtilaI benchmarked RefCounted against C++'s std::shared_ptr comparing ldc to clang using both shared and non-shared payloads in D. std::shared_ptr is faster (I've never written a smart pointer before), but the advantage of non-atomic operations makes my D implementation just a bit faster when non-shared. I think with some work it can be significantly faster without any loss of thread safety.Nice! You know you can overload `this(this) shared` do you? My plan was to use that for atomic RC, so that ppl. can use `shared(RefCounted)` when necessary.
Apr 09 2017
On Sunday, 9 April 2017 at 10:22:49 UTC, Atila Neves wrote:I did not. Thanks for telling me! The way I wrote it RefCounted!(shared T) works - RefCounted doesn't have to be shared itself, but I guess it could be.I think the other design is slightly more correct, having a single thread own a shared value => RefCounted!(shared T), having multiple threads own a value (which is transitively shared) => shared(RefCounted!T). The latter is also neede for `static shared RC!T rc;`.
Apr 11 2017
On Tuesday, 11 April 2017 at 08:09:15 UTC, Martin Nowak wrote:On Sunday, 9 April 2017 at 10:22:49 UTC, Atila Neves wrote:Unfortunately I later remembered that because it has a destructor it can't be shared and not shared with the sample implementation. And I'd really like to avoid having to have two different names like Rust does with Rc and Arc. AtilaI did not. Thanks for telling me! The way I wrote it RefCounted!(shared T) works - RefCounted doesn't have to be shared itself, but I guess it could be.I think the other design is slightly more correct, having a single thread own a shared value => RefCounted!(shared T), having multiple threads own a value (which is transitively shared) => shared(RefCounted!T). The latter is also neede for `static shared RC!T rc;`.
Apr 11 2017
On 4/9/17 4:56 AM, Atila Neves wrote:Using std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: http://code.dlang.org/packages/automem Example: I think the code in the README should be enough to understand what's going on. Alpha stuff here but I think the main things missing are weak pointers and a ref counted array. Given that I've never had to use std::weak_ptr in C++, I'm not in a hurry to implement the former.Nice work!Notable design decisions / features: . The smart pointers are responsible for allocating the memory for the container object using the allocator of choice. This is to guarantee that one can't allocate and deallocate using different allocators.Nice!. The allocator has to be specified as part of the type: this means the user can choose how to store it in the smart pointer, which for singletons (e.g. Mallocator) or stateless allocators means they can take up zero space. If a singleton (or the default theAllocator), the allocator doesn't need to be passed in to the constructor, otherwise it does. Specifying, e.g. Mallocator also means the relevant code can be marked nogc.After extensively studying how C++ allocator framework works, I got to the notion that making the allocator part of the type is an antipattern.. RefCounted only increments/decrements the ref count atomically if the contained type is `shared`Great. Can RefCounted itself be shared? I learned this is important for composition, i.e. you want to make a RefCounted a field in another object that is itself shared, immutable etc.. RefCounted!(shared T) can be sent to other threads.Awes.. UniqueArray behaves nearly like a normal array. You can even append to it, but it won't use GC memory (unless, of course, you chose to use GCAllocator)!This may be a great candidate for the standard library. Andrei
Apr 09 2017
On 09/04/2017 2:59 PM, Andrei Alexandrescu wrote:On 4/9/17 4:56 AM, Atila Neves wrote:snipTo further this, I should rewrite my managed memory concept, it would be very useful for e.g. collections if at the same quality as Atila's as it gives the creator (of the memory) a heck a lot of control over it. [0] https://github.com/rikkimax/alphaPhobos/blob/master/source/std/experimental/memory/managed.d. UniqueArray behaves nearly like a normal array. You can even append to it, but it won't use GC memory (unless, of course, you chose to use GCAllocator)!This may be a great candidate for the standard library.
Apr 09 2017
On Sunday, 9 April 2017 at 13:59:14 UTC, Andrei Alexandrescu wrote:On 4/9/17 4:56 AM, Atila Neves wrote:I was aware of this, but here we have a crucial workaround - theAllocator, which is the default anyway. It's probably the best of both worlds, since you can still specify the type if needed, which also means the guarantee of nogc if needed.Using std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: http://code.dlang.org/packages/automem Example: I think the code in the README should be enough to understand what's going on. Alpha stuff here but I think the main things missing are weak pointers and a ref counted array. Given that I've never had to use std::weak_ptr in C++, I'm not in a hurry to implement the former.Nice work!Notable design decisions / features: . The smart pointers are responsible for allocating the memory for the container object using the allocator of choice. This is to guarantee that one can't allocate and deallocate using different allocators.Nice!. The allocator has to be specified as part of the type: this means the user can choose how to store it in the smart pointer, which for singletons (e.g. Mallocator) or stateless allocators means they can take up zero space. If a singleton (or the default theAllocator), the allocator doesn't need to be passed in to the constructor, otherwise it does. Specifying, e.g. Mallocator also means the relevant code can be marked nogc.After extensively studying how C++ allocator framework works, I got to the notion that making the allocator part of the type is an antipattern.Since it has a destructor, no: http://forum.dlang.org/post/sqazguejrcdtjimtjxtz forum.dlang.org The only way to do that would be to split it into two. Which I guess I could with a template mixin implementing the guts.. RefCounted only increments/decrements the ref count atomically if the contained type is `shared`Great. Can RefCounted itself be shared? I learned this is important for composition, i.e. you want to make a RefCounted a field in another object that is itself shared, immutable etc.I think this needs to be used in production first, and having it as a dub package makes it easy for people to do so. Atila. RefCounted!(shared T) can be sent to other threads.Awes.. UniqueArray behaves nearly like a normal array. You can even append to it, but it won't use GC memory (unless, of course, you chose to use GCAllocator)!This may be a great candidate for the standard library.
Apr 10 2017
On Monday, 10 April 2017 at 08:11:37 UTC, Atila Neves wrote:On Sunday, 9 April 2017 at 13:59:14 UTC, Andrei Alexandrescu wrote:Syntax is not the core of the issue, it's not about just marking a destructor as shared. Making RefCounted itself shared would require implementing some form of synchronization of all the 'dereference' operations, including assignments. I.e. if we have some shared(RefCounted!T) ptr, what should happen when two threads simultaneously attempt to do ptr = shared(RefCounted!T)(someNewValue) ? Should a library implementation even consider this? Or should such synchronization be left to client's care? It seems like in this regard shared(RefCounted!T) would be no different from shared(T*), which brings me to the next point. On Andrei's comment regarding composition: if we're thinking about the design of shared data, IMHO, copying and shared are (or at least, should be) mutually exclusive. The only exception being references (pointers), and even for those care should be taken (which is one of the reasons of even having something like a RefCounted). If you can make a copy, there is no reason to share, or, conversely, if you intend to share, why would you copy? From that follows that a shared object should not ever have a RefCounted field. A Unique field, perhaps, but not a RefCounted. RefCounted's purpose is to be copied, not shared between threads. Unless I'm missing something.Great. Can RefCounted itself be shared? I learned this is important for composition, i.e. you want to make a RefCounted a field in another object that is itself shared, immutable etc.Since it has a destructor, no: http://forum.dlang.org/post/sqazguejrcdtjimtjxtz forum.dlang.org The only way to do that would be to split it into two. Which I guess I could with a template mixin implementing the guts.
Apr 12 2017
On Wednesday, 12 April 2017 at 13:32:36 UTC, Stanislav Blinov wrote:Syntax is not the core of the issue, it's not about just marking a destructor as shared. Making RefCounted itself shared would require implementing some form of synchronization of all the 'dereference' operations, including assignments. I.e. if we have some shared(RefCounted!T) ptr, what should happen when two threads simultaneously attempt to do ptr = shared(RefCounted!T)(someNewValue) ? Should a library implementation even consider this? Or should such synchronization be left to client's care? It seems like in this regard shared(RefCounted!T) would be no different from shared(T*), which brings me to the next point.If we can control memory layout, we can do what shared_ptr does and couple the reference counter with the object, then we can have just one pointer: struct RefCounted(T) { struct Wrapper { int count; T payload; } Wrapper* payload; }
Apr 17 2017
On Monday, 17 April 2017 at 13:21:50 UTC, Kagamin wrote:If we can control memory layout, we can do what shared_ptr does and couple the reference counter with the object, then we can have just one pointer: struct RefCounted(T) { struct Wrapper { int count; T payload; } Wrapper* payload; }I'm not sure I follow your comment. Indeed, that is how shared_ptr, or, in this case, RefCounted, is implemented. My point was that there is no practical sense in having a shared(RefCounted).
Apr 17 2017
On Sunday, 9 April 2017 at 13:59:14 UTC, Andrei Alexandrescu wrote:Just repeating an older argument of mine which didn't make it during the std.allocator discussions (http://forum.dlang.org/post/ubithltzbtdypaegnhvi forum.dlang.org). Yes, we clearly want type erasure for the Allocator, because having the Allocator as part of the type tends to push Allocator choices/forwarding everywhere into the APIs. It also prevents conversion/copying values with different allocators. The obvious solution is to use an Allocator interface and/or a custom deleter function (deleter was needed for attribute correct destruction with polymorphic RC!Klass, see [¹]). Now as Chandler Carruth mentions, https://www.youtube.com/watch?v=fHNmRkzxHWs&t=3950 https://www.youtube.com/watch?v=fHNmRkzxHWs&t=4037 , an interface with dynamic dispatch would prevent optimizing away redundant allocations. Allocations are complex enough that I'm not too worried about the virtual call overhead itself. I think we might be able to solve this problem in D by making IAllocator.allocate pure, which tells the compiler that this function returns a fresh piece of memory without any side-effect, i.e. enough information to optimize away allocations. Pure might be too restrictive for some allocators, but maybe this can be solved with a little type system hack (or at worse a compiler exemption). [¹]: https://github.com/MartinNowak/phobos/commit/8cf0ec29ad65ac2a13bd6917b4ff3da0fdea5ab0#diff-4e008aedb3026d4a84f58323e53bf017R4896. The allocator has to be specified as part of the type: this means the user can choose how to store it in the smart pointer, which for singletons (e.g. Mallocator) or stateless allocators means they can take up zero space. If a singleton (or the default theAllocator), the allocator doesn't need to be passed in to the constructor, otherwise it does. Specifying, e.g. Mallocator also means the relevant code can be marked nogc.After extensively studying how C++ allocator framework works, I got to the notion that making the allocator part of the type is an antipattern.
Apr 11 2017
On Tuesday, 11 April 2017 at 09:53:46 UTC, Martin Nowak wrote:I think we might be able to solve this problem in D by making IAllocator.allocate pure, which tells the compiler that this function returns a fresh piece of memory without any side-effect, i.e. enough information to optimize away allocations. Pure might be too restrictive for some allocators, but maybe this can be solved with a little type system hack (or at worse a compiler exemption).In LDC we have an attribute for that `allocSize` (https://github.com/ldc-developers/druntime/blob/ldc/src/ldc/attributes.d#L16) perhaps this attribute should be used across compilers and be in druntime?
Apr 11 2017
On Tuesday, 11 April 2017 at 10:24:08 UTC, Nicholas Wilson wrote:In LDC we have an attribute for that `allocSize` (https://github.com/ldc-developers/druntime/blob/ldc/src/ldc/attributes.d#L16) perhaps this attribute should be used across compilers and be in druntime?Nice, if pure required strong purity, it would be quite a huge hack, so a specific attribute seems friendlier.
Apr 11 2017
On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:Using std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: http://code.dlang.org/packages/automemI think that the Array misses - a reservation strategy, something like reserve() and allocBy(). - dup / idup that return new distinct and deep copies. - maybe .ptr at least for reading with pointer arithmetic. - opBinary for "~" . Also you have bugs with operators: ```d import std.experimental.allocator.mallocator; UniqueArray!(int, Mallocator) a; a ~= [0,1]; ``` crashes directly.
Apr 09 2017
On Sunday, 9 April 2017 at 15:52:50 UTC, Basile B. wrote:On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:Thanks for the suggestions.Using std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: http://code.dlang.org/packages/automemI think that the Array misses - a reservation strategy, something like reserve() and allocBy(). - dup / idup that return new distinct and deep copies. - maybe .ptr at least for reading with pointer arithmetic. - opBinary for "~" . Also you have bugs with operators:```d import std.experimental.allocator.mallocator; UniqueArray!(int, Mallocator) a; a ~= [0,1]; ``` crashes directly.Fixed now, thanks. Atila
Apr 10 2017
On Monday, 10 April 2017 at 08:31:28 UTC, Atila Neves wrote:So the difference between std.container.Array and UniqueArray is that the latter supports allocators?```d import std.experimental.allocator.mallocator; UniqueArray!(int, Mallocator) a; a ~= [0,1]; ```
Apr 11 2017
On Tuesday, 11 April 2017 at 22:32:51 UTC, Martin Nowak wrote:On Monday, 10 April 2017 at 08:31:28 UTC, Atila Neves wrote:That's the general idea, but I confess I didn't even look at Array. AtilaSo the difference between std.container.Array and UniqueArray is that the latter supports allocators?```d import std.experimental.allocator.mallocator; UniqueArray!(int, Mallocator) a; a ~= [0,1]; ```
Apr 11 2017
On Sunday, 9 April 2017 at 15:52:50 UTC, Basile B. wrote:On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:reserve is done. What would allocBy be?Using std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: http://code.dlang.org/packages/automemI think that the Array misses - a reservation strategy, something like reserve() and allocBy().- dup / idup that return new distinct and deep copies.dup is done. I'm trying to figure out how one would use .idup.- maybe .ptr at least for reading with pointer arithmetic.Done.- opBinary for "~" . Also you have bugs with operators:Done.```d import std.experimental.allocator.mallocator; UniqueArray!(int, Mallocator) a; a ~= [0,1]; ``` crashes directly.Fixed. Atila
Apr 28 2017
On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:Using std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: [...]Nice! Should UniqueArray be implemented as a overloaded version of Unique? Unique!(Object[]) instead of UniqueArray!(Object).
Apr 09 2017
On Sunday, 9 April 2017 at 19:04:22 UTC, mogu wrote:On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:I started like that, but after many a `static if` realised they had very little in common. AtilaUsing std.experimental.allocator? Tired of writing `scope(exit) allocator.dispose(foo);` in a language with RAII? Me too: [...]Nice! Should UniqueArray be implemented as a overloaded version of Unique? Unique!(Object[]) instead of UniqueArray!(Object).
Apr 10 2017
On Sunday, 9 April 2017 at 08:56:52 UTC, Atila Neves wrote:http://code.dlang.org/packages/automemYou might find my own containers interesting, especially https://github.com/nordlow/phobos-next/blob/master/src/array_ex.d Supports all the different ways I could think an array needs to work: https://github.com/nordlow/phobos-next/blob/master/src/array_ex.d#L64 https://github.com/nordlow/phobos-next/blob/master/src/array_ex.d#L90 https://github.com/nordlow/phobos-next/blob/master/src/array_ex.d#L1677 Does not (yet) support custom allocators, though. But that can be added, if requested. Are now in extensive use in my (non-public) applications.
Apr 11 2017