January 21, 2017
On Saturday, 21 January 2017 at 10:15:46 UTC, ZombineDev wrote:
> On Saturday, 21 January 2017 at 09:56:29 UTC, ZombineDev wrote:
>> On Saturday, 21 January 2017 at 02:47:49 UTC, bitwise wrote:
>>> When std.experimental.allocator was created, I was under the impression it was meant to be used to make containers. But since allocate() returns void[], casts are required before using that memory, which is unsafe.
>>>
>>> Is my only option to wrap the casts in an @trusted helper function? or am I missing something?
>>>
>>>  Thanks
>>
>> There's no need for casts. You can just use the high-level wrappers:
>> http://dlang.org/phobos-prerelease/std_experimental_allocator#.make
>>
>> http://dlang.org/phobos-prerelease/std_experimental_allocator#.makeArray
>>
>> http://dlang.org/phobos-prerelease/std_experimental_allocator#.dispose
>>
>> http://dlang.org/phobos-prerelease/std_experimental_allocator#.makeMultidimensionalArray
>>
>> http://dlang.org/phobos-prerelease/std_experimental_allocator#.disposeMultidimensionalArray
>
> Though, since you are implementing a container that manually manages memory, you won't be able to get away with no @trusted parts in your code. http://dlang.org/phobos-prerelease/std_experimental_allocator#.expandArray can help, but you'll still need to call dispose/deallocate somewhere yourself and you're the only that knows when is safe to do this.
> If you look at how Rust's standard library is implemented, you'll see the same pattern. The users have a safe API, but underneath you'll that the library uses many 'unsafe' blocks where the compiler doesn't see the full picture which only the author of the code knows.

I guess you're right. This is my first time looking at @safe in detail. I figured something like malloc() could @trusted, but I suppose free cannot.

I pictured being able to use an @safe allocator with an @safe container. How else could one reliably create @safe containers, or any @safe object for that matter, that allows the substitution of arbitrary and possibly unsafe components?

I guess the answer is responsible programming, but that seems counter intuitive given the goals of @safe.

January 21, 2017
On Saturday, 21 January 2017 at 16:28:16 UTC, Andrei Alexandrescu wrote:
> alignedAllocate provides access to OS/clib-provided primitives for aligned allocation. Those don't require a special deallocation function, see e.g. http://en.cppreference.com/w/c/memory/aligned_alloc. -- Andrei

This makes sense, but then shouldn't alignedAllocate() be a free function that could be used to make an aligned allocator?

Again, the point of an allocator is to provide a standard interface for memory allocation. The user of which shouldn't have to know how that memory was allocated, and I can't think of a case where this would be desired either.
January 21, 2017
On 01/21/2017 12:14 PM, bitwise wrote:
> On Saturday, 21 January 2017 at 16:28:16 UTC, Andrei Alexandrescu wrote:
>> alignedAllocate provides access to OS/clib-provided primitives for
>> aligned allocation. Those don't require a special deallocation
>> function, see e.g.
>> http://en.cppreference.com/w/c/memory/aligned_alloc. -- Andrei
>
> This makes sense, but then shouldn't alignedAllocate() be a free
> function that could be used to make an aligned allocator?

You mean an aligned allocation? But then the actual method, and whether it is supported at all or not, depends on the allocator.

> Again, the point of an allocator is to provide a standard interface for
> memory allocation. The user of which shouldn't have to know how that
> memory was allocated, and I can't think of a case where this would be
> desired either.

I don't understand this. It's possible there's a confusion at a different level, and we're discussing the consequences.

To reboot:

* Some applications need memory aligned at special powers of two, most are okay with more generic alignments such as word-level. See the "alignment" enum that each allocator must define.

* Some allocators do offer allocations at unusual powers of two by means of alignedAllocate. Don't call it naively! Only applications that need such things (e.g. page-aligned, cache-line-aligned, etc) should need those.

* Those allocators must be able to deallocate memory allocated with allocate() or alignedAllocate() with a single primitive deallocate().


Andrei
January 21, 2017
On Saturday, 21 January 2017 at 17:26:35 UTC, Andrei Alexandrescu wrote:
> On 01/21/2017 12:14 PM, bitwise wrote:
>> On Saturday, 21 January 2017 at 16:28:16 UTC, Andrei Alexandrescu wrote:
>>> alignedAllocate provides access to OS/clib-provided primitives for
>>> aligned allocation. Those don't require a special deallocation
>>> function, see e.g.
>>> http://en.cppreference.com/w/c/memory/aligned_alloc. -- Andrei
>>
>> This makes sense, but then shouldn't alignedAllocate() be a free
>> function that could be used to make an aligned allocator?
>
> You mean an aligned allocation? But then the actual method, and whether it is supported at all or not, depends on the allocator.
>
>> Again, the point of an allocator is to provide a standard interface for
>> memory allocation. The user of which shouldn't have to know how that
>> memory was allocated, and I can't think of a case where this would be
>> desired either.
>
> I don't understand this. It's possible there's a confusion at a different level, and we're discussing the consequences.
>
> To reboot:
>
> * Some applications need memory aligned at special powers of two, most are okay with more generic alignments such as word-level. See the "alignment" enum that each allocator must define.
>
> * Some allocators do offer allocations at unusual powers of two by means of alignedAllocate. Don't call it naively! Only applications that need such things (e.g. page-aligned, cache-line-aligned, etc) should need those.
>
> * Those allocators must be able to deallocate memory allocated with allocate() or alignedAllocate() with a single primitive deallocate().
>
>
> Andrei

About alignedMalloc:

In C++ for example, I may want to use a vector full some SIMD type:

class alignas(16) Vec4 {
    union {
        struct { float x, y, z, w; };
        __m128 m;
    };
};

std::vector<Vec4> points = { ... };

In C++ however, 'new' does not respect over-alignment:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0035r2.html

Even if new respected alignment, there is no gauruntee all containers, STL or otherwise, would use 'new' as opposed to malloc by default(maybe one day?)

So I use a custom aligned allocator:

template <class T, int ALIGN>
class AlignedAllocator {
    T* allocate(size_type n) {
        return (T*)_aligned_malloc(ALIGN, n * sizeof(T));
    }
};

SIMD operations(aligned load and store) can now safely be used on the contents of the std::vector<Vec4>.

std::vector knows nothing about the alignment of the memory it uses. It only knows to call allocate() of whatever allocator it's given. If I had an allocator with a function 'alignedAllocate' it wouldn't do any good. I believe this is the _correct_ design, and that a container _shouldn't_ have to know about where from, or what kind of memory it's getting.

Considering the above use case, alignedAllocate() is redundant, and possibly confusing.

About missing alignedDeallocate:

while aligned_alloc(), which works in combination with regular 'free()', is supposed to be standard as of C++11, it's still not supported in visual studio 2015. Instead, one must use _aligned_malloc, and _aligned_free. Passing memory from _aligned_malloc to the regular version of free() causes a crash. Thus, different deallocation methods are needed for both. Also, there's homegrown aligned_allocate functions like the following, which require special deallocation functions because of the exta metadata prepended to the memory:
https://github.com/dlang/phobos/blob/366f6e4e66abe96bca9fd69d03042e08f787d040/std/experimental/allocator/mallocator.d#L134-L134

I suppose you could use aligned allocation for _all_ allocations, even allocations with default alignment, but that would add extra metadata(at least 8 bytes) to _all_ allocations even when its unnecessary.

So a solution could be to include the alignment as a template parameter of Mallocator, or provide an second AlignedMallocator(uint). The allocate() function of either option would return aligned memory if the 'alignment' template parameter was non-default. Then, the idea of memory alignment would be abstracted away from the containers themselves.

struct Mallocator(uint alignment = platformAlignment){}){}
or
struct AlignedMallocator(uint alignment = platformAlignment){}){}


January 21, 2017
On 1/21/17 5:44 PM, bitwise wrote:
> About alignedMalloc:
>
> In C++ for example, I may want to use a vector full some SIMD type:
>
> class alignas(16) Vec4 {
>     union {
>         struct { float x, y, z, w; };
>         __m128 m;
>     };
> };
>
> std::vector<Vec4> points = { ... };
>
> In C++ however, 'new' does not respect over-alignment:
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0035r2.html
>
> Even if new respected alignment, there is no gauruntee all containers,
> STL or otherwise, would use 'new' as opposed to malloc by default(maybe
> one day?)
>
> So I use a custom aligned allocator:
>
> template <class T, int ALIGN>
> class AlignedAllocator {
>     T* allocate(size_type n) {
>         return (T*)_aligned_malloc(ALIGN, n * sizeof(T));
>     }
> };
>
> SIMD operations(aligned load and store) can now safely be used on the
> contents of the std::vector<Vec4>.
>
> std::vector knows nothing about the alignment of the memory it uses. It
> only knows to call allocate() of whatever allocator it's given. If I had
> an allocator with a function 'alignedAllocate' it wouldn't do any good.
> I believe this is the _correct_ design, and that a container _shouldn't_
> have to know about where from, or what kind of memory it's getting.

I understand. That's a questionable design. It only works by virtue of a long-distance convention between the rigged allocator and the element type of the vector.

> Considering the above use case, alignedAllocate() is redundant, and
> possibly confusing.

Well, you just made use of it in the rigged allocator.

> About missing alignedDeallocate:
>
> while aligned_alloc(), which works in combination with regular 'free()',
> is supposed to be standard as of C++11, it's still not supported in
> visual studio 2015. Instead, one must use _aligned_malloc, and
> _aligned_free. Passing memory from _aligned_malloc to the regular
> version of free() causes a crash. Thus, different deallocation methods
> are needed for both. Also, there's homegrown aligned_allocate functions
> like the following, which require special deallocation functions because
> of the exta metadata prepended to the memory:
> https://github.com/dlang/phobos/blob/366f6e4e66abe96bca9fd69d03042e08f787d040/std/experimental/allocator/mallocator.d#L134-L134
>
>
> I suppose you could use aligned allocation for _all_ allocations, even
> allocations with default alignment, but that would add extra metadata(at
> least 8 bytes) to _all_ allocations even when its unnecessary.
>
> So a solution could be to include the alignment as a template parameter
> of Mallocator, or provide an second AlignedMallocator(uint). The
> allocate() function of either option would return aligned memory if the
> 'alignment' template parameter was non-default. Then, the idea of memory
> alignment would be abstracted away from the containers themselves.
>
> struct Mallocator(uint alignment = platformAlignment){}){}
> or
> struct AlignedMallocator(uint alignment = platformAlignment){}){}

It seems a matter of time until aligned_alloc gets implemented on Windows.


Andrei


January 22, 2017
On Sat, 21 Jan 2017 22:44:49 +0000, bitwise wrote:
> So I use a custom aligned allocator:
> 
> template <class T, int ALIGN>
> class AlignedAllocator {
>      T* allocate(size_type n) {
>          return (T*)_aligned_malloc(ALIGN, n * sizeof(T));
>      }
> };
> 
> SIMD operations(aligned load and store) can now safely be used on the contents of the std::vector<Vec4>.

This is fine when you have fine-grained control of which allocators are used for which purpose and you know more about what the code needs than whoever is consuming the allocator.

alignedAllocate() is for when the code consuming the allocator knows more about alignment needs than whoever is supplying the allocator, which is more likely to crop up with coarse-grained allocator control.
January 22, 2017
On Sunday, 22 January 2017 at 00:13:10 UTC, Chris Wright wrote:
> On Sat, 21 Jan 2017 22:44:49 +0000, bitwise wrote:
>> So I use a custom aligned allocator:
>> 
>> template <class T, int ALIGN>
>> class AlignedAllocator {
>>      T* allocate(size_type n) {
>>          return (T*)_aligned_malloc(ALIGN, n * sizeof(T));
>>      }
>> };
>> 
>> SIMD operations(aligned load and store) can now safely be used on the contents of the std::vector<Vec4>.
>
>
> alignedAllocate() is for when the code consuming the allocator knows more about alignment needs than whoever is supplying the allocator, which is more likely to crop up with coarse-grained allocator control.

I understand this, but I've given a solid example which supports the former, and haven't seen a counter example which supports the latter.


January 22, 2017
On Sun, 22 Jan 2017 00:53:45 +0000, bitwise wrote:
> I understand this, but I've given a solid example which supports the former, and haven't seen a counter example which supports the latter.

Because examples that are easy to generate are small and small examples don't have coarse-grained anything.
January 22, 2017
On Saturday, 21 January 2017 at 23:24:52 UTC, Andrei Alexandrescu wrote:
> On 1/21/17 5:44 PM, bitwise wrote:
>> About alignedMalloc:
>>
>> In C++ for example, I may want to use a vector full some SIMD type:
>>
>> class alignas(16) Vec4 {
>>     union {
>>         struct { float x, y, z, w; };
>>         __m128 m;
>>     };
>> };
>>
>> std::vector<Vec4> points = { ... };
>>
>> In C++ however, 'new' does not respect over-alignment:
>> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0035r2.html
>>
>> Even if new respected alignment, there is no gauruntee all containers,
>> STL or otherwise, would use 'new' as opposed to malloc by default(maybe
>> one day?)
>>
>> So I use a custom aligned allocator:
>>
>> template <class T, int ALIGN>
>> class AlignedAllocator {
>>     T* allocate(size_type n) {
>>         return (T*)_aligned_malloc(ALIGN, n * sizeof(T));
>>     }
>> };
>>
>> SIMD operations(aligned load and store) can now safely be used on the
>> contents of the std::vector<Vec4>.
>>
>> std::vector knows nothing about the alignment of the memory it uses. It
>> only knows to call allocate() of whatever allocator it's given. If I had
>> an allocator with a function 'alignedAllocate' it wouldn't do any good.
>> I believe this is the _correct_ design, and that a container _shouldn't_
>> have to know about where from, or what kind of memory it's getting.
>
> I understand. That's a questionable design. It only works by virtue of a long-distance convention between the rigged allocator and the element type of the vector.

I don't understand what's questionable about it. I don't see how abstracting the alignment away from the consumer of an allocator is a bad thing.

>> Considering the above use case, alignedAllocate() is redundant, and
>> possibly confusing.
>
> Well, you just made use of it in the rigged allocator.

I made use of what I would expect to be a non-member helper function. I'm saying that I don't believe alignedAllocate() should be a part of the standard interface of an allocator, and that allocators should be specialized such that allocate() returns memory with whatever alignment is needed.


>> About missing alignedDeallocate:
>>
>> while aligned_alloc(), which works in combination with regular 'free()',
>> is supposed to be standard as of C++11, it's still not supported in
>> visual studio 2015. Instead, one must use _aligned_malloc, and
>> _aligned_free. Passing memory from _aligned_malloc to the regular
>> version of free() causes a crash. Thus, different deallocation methods
>> are needed for both. Also, there's homegrown aligned_allocate functions
>> like the following, which require special deallocation functions because
>> of the exta metadata prepended to the memory:
>> https://github.com/dlang/phobos/blob/366f6e4e66abe96bca9fd69d03042e08f787d040/std/experimental/allocator/mallocator.d#L134-L134
>>
>>
>> I suppose you could use aligned allocation for _all_ allocations, even
>> allocations with default alignment, but that would add extra metadata(at
>> least 8 bytes) to _all_ allocations even when its unnecessary.
>>
>> So a solution could be to include the alignment as a template parameter
>> of Mallocator, or provide an second AlignedMallocator(uint). The
>> allocate() function of either option would return aligned memory if the
>> 'alignment' template parameter was non-default. Then, the idea of memory
>> alignment would be abstracted away from the containers themselves.
>>
>> struct Mallocator(uint alignment = platformAlignment){}){}
>> or
>> struct AlignedMallocator(uint alignment = platformAlignment){}){}
>
> It seems a matter of time until aligned_alloc gets implemented on Windows.

But how much time? Visual studio always lags behind in standards conformance.

Also, there is still the fact that some may need to use home-grown aligned allocation functions like the ones I linked above that prepend metadata to the memory returned, in which case they will need specialized deallocation functions.
January 22, 2017
On Sunday, 22 January 2017 at 01:02:09 UTC, Chris Wright wrote:
> On Sun, 22 Jan 2017 00:53:45 +0000, bitwise wrote:
>> I understand this, but I've given a solid example which supports the former, and haven't seen a counter example which supports the latter.
>
> Because examples that are easy to generate are small and small examples don't have coarse-grained anything.

Please explain