RFC: moving forward with @nogc Phobos

via Digitalmars-d digitalmars-d at puremagic.com
Wed Oct 1 13:51:06 PDT 2014


On Wednesday, 1 October 2014 at 15:48:39 UTC, Oren Tirosh wrote:
> On Tuesday, 30 September 2014 at 19:10:19 UTC, Marc Schütz 
> wrote:
> One problem with actually implementing this is that using 
> reference counting as a memory management policy requires extra 
> space for the reference counter in the object, just as garbage 
> collection requires support for scanning and identification of 
> interior object memory range. While allocation and memory 
> management may be quite independent in theory, practical high 
> performance implementations tend to be intimately related.
>
>> (I'll try to make a sketch on how this can be implemented in 
>> another post.)
>
> Do elaborate!
>
>> As a conclusion, I would say that APIs should strive for the 
>> following principles, in this order:
>>
>> 1. Avoid allocation altogether, for example by laziness 
>> (ranges), or by accepting sinks.
>>
>> 2. If allocations are necessary (or desirable, to make the API 
>> more easily usable), try hard to return a unique value (this 
>> of course needs to be expressed in the return type).
>>
>> 3. If both of the above fails, only then return a GCed 
>> pointer, or alternatively provide several variants of the 
>> function (though this shouldn't be necessary often). An 
>> interesting alternative: Instead of passing a flag directly 
>> describing the policy, pass the function a type that it should 
>> wrap it's return value in.
>>
>> As for the _allocation_ strategy: It indeed needs to be 
>> configurable, but here, the same objections against a template 
>> parameter apply. As the allocator doesn't necessarily need to 
>> be part of the type, a (thread) global variable can be used to 
>> specify it. This lends itself well to idioms like
>>
>>    with(MyAllocator alloc) {
>>        // ...
>>    }
>
> Assuming there is some dependency between the allocator and the 
> memory management policy I guess this would be initialized on 
> thread start that cannot be modified later. All code running 
> inside the thread would need to either match the configured 
> policy, not handle any kind of pointers or use a limited subset 
> of unique pointers. Another way to ensure that code can run on 
> either RC or GC is to make certain objects (specifically, 
> Exceptions) always allocate a reference counter, regardless of 
> the currently configured policy.

I don't have all answers to these questions. Still, I'm convinced 
this is doable.

A straight-forwarding and general way to convert a unique object 
to a ref-counted one is to allocate new memory for it plus the 
reference count, move the original object into it, and release 
the original memory. This is safe, because there can be no 
external pointers to the object, as it is unique. Of course, this 
can be optimized if the allocator supports extending an 
allocation. It could then preallocate a few extra bytes at the 
end to make the extend operation always succeed, similar to your 
suggestion to always allocate a reference counter.

I think the most difficult part is to find an efficient and 
user-friendly way for the wrapper types to get at the allocator. 
Maybe the allocators should all implement an interface (a real 
one, not duck-typing). The wrappers (Owned, RC) can then include 
a pointer to the allocator (or for RC, embed it next to the 
reference count). This would make it possible to specify a 
(thread) global default allocator at runtime, which all library 
functions use by convention (for example let's call it `alloc`, 
then they would call `alloc.make!MyStruct()`). At the same time, 
it is safe to change the default allocator at any time, and to 
use different allocators in parallel in the same thread.

The alternative is obviously a template parameter to the function 
that returns the unique object. But this unfortunately is then 
not restricted to just the function, but "infects" the return 
type, too. And from there, it needs to spread to the RC wrapper, 
or any containers. Thus we'd have incompatible RC types, which I 
would imagine would be very inconvenient and restrictive. 
Besides, it would probably be too tedious to specify the 
allocator everywhere.

Therfore, I think the additional cost of an allocator interface 
pointer is worth it. For Owned!T (with T being a pointer or 
reference), it would just be two words, which we can return 
efficiently. We already have slices doing that, and AFAIK there's 
no significantly worse performance because of them.


More information about the Digitalmars-d mailing list