Partydonk / partydonk

MIT License
33 stars 1 forks source link

Introduce Reference Counting in .NET #6

Open migueldeicaza opened 4 years ago

migueldeicaza commented 4 years ago

Automatic Reference Counting Base Type

We need to introduce an ARC-like style way of flagging types, so whenever those are used, a Release call is made, so we can promptly dispose of that data. This would not solve the problem for reference counted objects in regular classes, but would solve it for local variables, or those contained themselves in reference counted classes.

This is important because when working with Tensors, each object really is a pointer to an expensive resource, but Disposing those manually is too painful.

Proposal:

namespace System {
  interface IReferenceCounted {
     void AddRef ();
     void Release ()
  }

  // Provides a base implementation that calls Dispose
  public class ReferenceCounted : IReferenceCounted, IDisposable {
     public void Release ()
     {
         if (InterlockedCompareAndExchange (counter - 1) == 0){
              // Debatable, would be nicer to use a Dispose(bool dispoing) call
              // but that is a convention, not an interface today.
              Dispose ();
         }
     }
  }
}

Classes that implement IReferenceCounted would have the compiler or runtime inject calls to AddRef every time they are assigned and call Release when the variable goes out of scope.

Usage:

class Tensor : ReferenceCounted {
    public void Dispose ()
    {
        ...
        NativeMethod.ReleaseTensor (handle);
        ..
    }
}

void LoadData (Input inp, Model m, Tensor prevLayer)
{
    Tensor accumulated = Tensor.zeros (prevLayer.Shape);

    while ((var loaded = LoadTensorFrom (inp)) != null){
       accumulated = loaded + prevLayer;
       m.Forward (accumulated);
    }
}

In the example above, the assignment to accumulated would call Release inside the while loop, before the value is assigned, and would call AddRef on the result from the loaded + prevLayer operation. It would also take a reference in the call to m.Forward, and call Release immediately afterwards.

Given that these refcounted objects are going to be used to wrap heavy resources, we think that these should use atomic operations to increase/decrease the count, which would cause the least surprises.

Variations on the above proposal include not having to subclass ReferecedCounted, but we could make it an attribute [RefCounted] on the type declaration.

Jan Kotas suggests that we should make AddRef and ReleaseRef methods implemented by the runtime, and not by the user. This would avoid problems caused by users throwing exceptions in those methods potentially leaking data.

Update October 2, 2020: We could explore having a Cecil-based rewriter for assemblies as a temporary band-aid until this comes to compilers. It would process assemblies that include a reference to IRefereceCounted and would track all loads/stores in IL to inject the proper code.

benaadams commented 4 years ago

Would reference counting thread safe?

YairHalberstadt commented 4 years ago

What would happen if a reference counted object was stored as a field in a class? It would then not be deterministically disposed.

I think it would make sense to limit reference counted types to ref structs.