February 05, 2014
On Wednesday, 5 February 2014 at 19:42:26 UTC, Adam D. Ruppe wrote:
> On Wednesday, 5 February 2014 at 19:36:00 UTC, Namespace wrote:
>> 	static struct Payload {
>> 		void* ptr;
>> 		int* rc;
>> 	}
>>
>> 	Payload* p;
>
> Now there's double indirection to get to the data... and you also forgot the necessary postblits and dtors to maintain the reference count. (these could be inlined or elided in some cases, but not all)
>
> You can't put the rc at the beginning of the pointer either, since then a = a[1..$] won't work. So the added pointer is unavoidable, your way or my way, both have a cost.

Hm, you're right. Would have been nice if the nice syntax could be retained, instead of further unsightly library solution.
February 05, 2014
On 2/5/14, 11:45 AM, "Ola Fosheim Grøstad" <ola.fosheim.grostad+dlang@gmail.com>" wrote:
> On Wednesday, 5 February 2014 at 19:37:29 UTC, Andrei Alexandrescu wrote:
>> This is only focusing on slices.
>
> That is not going to work out well, because you should be able to
> decrease the ref count of a pointer to something arbitrary (void)
> without knowing the object type.

I said it a couple of times, and it seems it bears repeating: the charter of this is solely to create a slice type that takes care of itself. What this is not is a general solution for managing internal pointers or pointers to arbitrary objects.

Andrei


February 05, 2014
On Tuesday, 4 February 2014 at 23:51:35 UTC, Andrei Alexandrescu
wrote:
> Consider we add a library slice type called RCSlice!T. It would have the same primitives as T[] but would use reference counting through and through. When the last reference count is gone, the buffer underlying the slice is freed. The underlying allocator will be the GC allocator.
>
> Now, what if someone doesn't care about the whole RC thing and aims at convenience? There would be a method .toGC that just detaches the slice and disables the reference counter (e.g. by setting it to uint.max/2 or whatever).
>
> Then people who want reference counting say
>
> auto x = fun();
>
> and those who don't care say:
>
> auto x = fun().toGC();
>
>
> Destroy.
>
> Andrei

I think I'd rather have a higher-level solution than this. I
would worry about having to reason about code that's littered
with toGC() or toARC() calls: my peanut butter (application
logic) is now mixed in with my chocolate (allocation logic),
making both harder to understand.

I thought there was some discussion about arenas and custom
allocators a while back, and that seemed like a sensible way to
address these issues. I had imagined (naively?) that I would have
been able to write something like:

auto someFunction(T)(T params)
{
   ComplicatedResult result;
   {
     // set up allocator at start of scope
     auto arena = GrowableArena();
     push_allocator(arena); // thread-local allocator
     scope(exit) pop_allocator();

     // call as many expensive functions as you like...
     auto tmp = allocation_intensive_computation(params);

     // transitive-move result from arena, at the scope's tail
     result = tmp.toGC();
   } // arena goes out of scope and is deallocated

   return result;
}

So, allocation and lifetime issues are handled at the boundaries
of scopes that have custom allocators associated with them.

Um... destroy?

Graham
February 05, 2014
On Wednesday, 5 February 2014 at 19:39:43 UTC, Andrei Alexandrescu wrote:
> You do figure that complicates usage considerably, right?

I don't see much evidence for that. Many, many newer modules in Phobos are currently allocation free yet still pretty easy to use.

A major source of little allocations in my code is std.conv and std.string. But these aren't difficult to change to external allocation, in theory at least:

string s = to!string(50); // GC allocates (I'd keep this for convenience and compatibility)

char[16] buffer;
char[] s = toBuffer(buffer[], 50); // same thing, using a buffer

char[] s = toLowerBuffer(buffer[], "FOO");
assert(buffer.ptr is s);
assert(s == "foo");


That's not hard to use (though remembering that s is a borrowed reference to a stack buffer might be - escape analysis is something we should really have).

And it gives full control over both allocation and deallocation. It'd take some changes in phobos, but so does the RCSlice sooo yeah, and this actually decouples it from the GC.

The tricky part might be making it work with buffers, growable buffers, sink functions, etc., but we've solved similar problems with input ranges.


> I was thinking RCSlice would be a better alternative.

I very rarely care about when little slices are freed. Large blocks of memory might be another story (I've used malloc+free for a big internal buffer in my png.d after getting memory leaks from false poitners with teh gc) but those can be handled on a case by case basis. std.base64 for example might make sense to return one of these animals.

I don't have a problem with refcounting on principle but most the time, it just doesn't matter.
February 05, 2014
On Wednesday, 5 February 2014 at 20:04:31 UTC, Andrei Alexandrescu wrote:
> I said it a couple of times, and it seems it bears repeating: the charter of this is solely to create a slice type that takes care of itself. What this is not is a general solution for managing internal pointers or pointers to arbitrary objects.

Then I think this is starting in the wrong end of the problem space. Slices are tiny dots in this picture.

It would be better to have a RC compiler switch and version{} statements in the libraries rather than having extensive special casing of RC/GC  vs pure GC types in user code.
February 05, 2014
On 2014-02-05 18:26:38 +0000, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> said:

> On 2/5/14, 7:23 AM, Michel Fortin wrote:
>> I don't think it makes much sense. ARC when used for D constructs should
>> be treated an alternate GC algorithm, not a different kind of pointer.
> 
> Why? The RC object has a different layout, so it may as well have a different type.

Well, it depends on your goal. If your goal is to avoid the garbage collector, you need all language constructs to use ARC. Having a single type in the language that relies on the GC defeats the purpose. What you want is simply to replace the current GC with another implantation, one that uses ARC. It shouldn't affect user code in any way, it's mostly an implementation detail (managed by the compiler and the runtime).

If your goal is to have a deterministic lifetime for slices in some situations, then RCSlice as you proposes it is fine. That said, with a library type you'll have a hard time making the optimizer elide redundant increment/decrement pairs, so it'll never be optimal. I'm also not sure there's a lot of use cases for a deterministic slice lifetime working side by side with memory managed by the current GC.

To me it seems you're trying to address a third problem here: that people have complained that Phobos relies on the GC too much. This comes from people who either don't want the GC to pause anything, or people who want to reduce memory allocations altogether. For the former group, replacing the current GC with an ARC+GC scheme at the language level, with the possibility to disable the GC, will fix most of Phobos (and most other libraries) with no code change required. For the later group, you need to make the API so that allocations are either not necessary, or when necessary provide a way to use a custom an allocator of some sort.


-- 
Michel Fortin
michel.fortin@michelf.ca
http://michelf.ca

February 05, 2014
On Wednesday, 5 February 2014 at 19:48:58 UTC, Namespace wrote:
> Hm, you're right. Would have been nice if the nice syntax could be retained, instead of further unsightly library solution.

The thing is most slices don't need anything special - they are inspected, but not stored. Since they aren't stored, the allocation isn't this function's problem.

Yesterday, I wrote a post with a function int average(int[] numbers) as an illustration here. numbers might be on the stack, the Gc heap, or a refcounted array, and none of that matters. Average just looks at it. As long as there isn't something like another thread that frees the memory in the middle of average's execution, it will be fine. (And if there is a magic thread freeing things willy nilly, now that's a real WTF!)


This is why the borrowed idea (implemented by escape analysis in my mind, i think that would work and get us most the benefits without all of Rust's complexity) is nice: with a borrowed pointer, you explicitly know freeing it isn't your problem. You don't have to count or carry a refcount, you don't have to run the gc, you don't have to call free. You can take a lightweight slice and use it with confidence... as long as it doesn't escape the scope and thus accidentally stick around after the function returns.
February 05, 2014
05-Feb-2014 03:51, Andrei Alexandrescu пишет:
> Consider we add a library slice type called RCSlice!T. It would have the
> same primitives as T[] but would use reference counting through and
> through. When the last reference count is gone, the buffer underlying
> the slice is freed. The underlying allocator will be the GC allocator.
>
> Now, what if someone doesn't care about the whole RC thing and aims at
> convenience? There would be a method .toGC that just detaches the slice
> and disables the reference counter (e.g. by setting it to uint.max/2 or
> whatever).
>
> Then people who want reference counting say
>
> auto x = fun();

How abut just adding a template argument that indicates which container type to use for internal allocation?

Array!T a = fun!(Array)(); //Ref-counted
T[] a = fun(); //default args - GC

IMHO solves Phobos side of equation.

-- 
Dmitry Olshansky
February 05, 2014
Am Wed, 05 Feb 2014 20:18:33 +0000
schrieb "Adam D. Ruppe" <destructionator@gmail.com>:

> On Wednesday, 5 February 2014 at 19:39:43 UTC, Andrei Alexandrescu wrote:
> > You do figure that complicates usage considerably, right?
> 
> I don't see much evidence for that. Many, many newer modules in Phobos are currently allocation free yet still pretty easy to use.
> 
> A major source of little allocations in my code is std.conv and std.string. But these aren't difficult to change to external allocation, in theory at least:
> 
> string s = to!string(50); // GC allocates (I'd keep this for convenience and compatibility)
> 
> char[16] buffer;
> char[] s = toBuffer(buffer[], 50); // same thing, using a buffer
> 
> char[] s = toLowerBuffer(buffer[], "FOO");
> assert(buffer.ptr is s);
> assert(s == "foo");
> 
> 

I think using a template parameter to allow for all kinds of allocators (std.allocator) is better. But of course it should have zero-overhead for a static-buffer allocator. Or we just special case static buffers _and_ add allocators ;-)

But as Andrei said that discussion is not part of this thread.



The main reason for RCSlice is not returning from Phobos functions, it's passing slices to Phobos functions.

You can always create a RCSlice!string with any kind of string and as Andrei wants them GC-backed anyway, you can just create the RCSlice!string after the function returned a GC allocated string. (As long as the function doesn't keep a reference)

But if we pass a RCd slice to a phobos function as a normal string, we'd have to stop reference counting. While this doesn't matter for GC-backed slices it basically means that manually allocated slices would never be freed.

However, I wonder if that's really a problem in phobos. I'd guess most functions accepting slice input don't store a reference. We should probably start documenting that. (Or finish 'scope' as you already said implicitly ;-).
February 05, 2014
On Wednesday, 5 February 2014 at 20:46:32 UTC, Johannes Pfau wrote:
> However, I wonder if that's really a problem in phobos. I'd guess most functions accepting slice input don't store a reference.
> We should probably start documenting that. (Or finish 'scope' as you already said implicitly ;-).

Aye. If the reference never escapes, it doesn't need to be counted or freed (indeed, it really MUST never be freed, since whomever passed you that reference may still be using it and is responsible for freeing it (or passing the buck to the GC))