On 2013-10-09 19:24:18 +0000, Walter Bright <newshou...@digitalmars.com> said:

On 10/9/2013 10:05 AM, Manu wrote:
Supporting ARC in the compiler _is_ the job. That includes a cyclic-reference
solution.

Wholly replacing the GC with ARC has some fundamental issues:

1. Array slicing becomes clumsy instead of fast & cheap.

I think you're exaggerating a bit.

If you slice, you know it's the same memory block, so you know it's using the same reference count, so the compiler can elide paired retain release calls just like it should be able to do for regular pointers, keeping it fast and cheap.

Example:

        int[] a = [1, 2]; // 1. retained on allocation
        // 2. scope(exit) release a
        int[] b = a[0..1]; // 3. retain on assignment
        // 4. scope(exit) release b
        return b; // 5. retain b for caller

Now, you know at line 3 that "b" is the same memory block as "a", so 2 and 3 cancels each other, and so do 4 and 5.

Result:

        int[] a = [1, 2]; // 1. retained on allocation
        int[] b = a[0..1];
        return b;

The assumption here is that int[] does not span over two memory blocks. Perhaps this is a problem for memory management @system code, but memory management @system code should be able to opt-out anyway, if only to be able to write the retain/release implementation. (In Objective-C you opt out using the __unsafe_unretained pointer attribute.)

That said, with function boundaries things are a little messier:

        int[] foo(int[] a) // a is implicitly retained by the caller
                           // for the duration of the call
        {
                int[] b = a[0..1]; // 1. retain on assignment
                // 2. scope(exit) release b
                return b; // 3. retain b for caller
        }

Here, only 1 and 2 can be elided, resulting in one explicit call to retain:

        int[] foo(int[] a) // a is implicitly retained by the caller
                           // for the duration of the call
        {
                int[] b = a[0..1];
                return b; // 3. retain b for caller
        }

But by inlining this trivial function, similar flow analysis in the caller should be able to elide that call to retain too.

So there is some overhead, but probably not as much as you think (and probably a little more than I think because of control flow and functions that can throw put in the middle of this are going to make it harder to elide redundant retain/release pairs). Remember that you have less GC overhead and possibly increased memory locality too (because memory is freed and reused sooner). I won't try to guess which is faster, it's probably going to differ depending on the benchmark anyway.


2. Functions can now accept new'd data, data from user allocations, static data, slices, etc., with aplomb. Wiring in ARC would likely make this unworkable.

That's no different from the GC having to ignore those pointers when it does a scan. Just check it was allocated within the reference counted allocator memory pool, and if so adjust the block's reference counter, else ignore. There's a small performance cost, but it's probably small compared to an atomic increment/decrement.

Objective-C too has some objects (NSString objects) in the static data segment. They also have "magic" hard-coded immutable value objects hiding the object's payload within the pointer itself on 64-bit processors. Calls to retain/release just get ignored for these.


3. I'm not convinced yet that ARC can guarantee memory safety. For example, where do weak pointers fit in with memory safety? (ARC uses user-annoted weak pointers to deal with cycles.)

Failure to use weak pointers creates cycles, but cycles are not unsafe. The worse that'll happen is memory exhaustion (but we could/should still have an optional GC available to collect cycles). If weak pointers are nulled automatically (as they should) then you'll never get a dangling pointer.

To use a weak pointer you first have to make a non-weak copy of it through a runtime call. You either get a null non-weak pointer or a non-null one if the object is still alive. Runtime stuff ensure that this works atomically with regard to the reference count falling to zero. No dangling pointer.


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

Reply via email to