[swift-evolution] RFC: Proposed rewrite of Unmanaged<T>

Dave Abrahams dabrahams at apple.com
Sun Feb 21 05:28:28 CST 2016


Back to this after a long hiatus, sorry.

on Tue Dec 29 2015, Janosch Hildebrand <jnosh-AT-jnosh.com> wrote:

>> On 19 Dec 2015, at 22:09, Dave Abrahams <dabrahams at apple.com> wrote:
>> 
>> 
>>> On Dec 18, 2015, at 6:18 PM, Janosch Hildebrand via swift-evolution
>>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>
>>> wrote:
>>> ...
>> 
>> I don't see any point in having "manuallyRelease()" if we already
>> have "release()"; they'd do the same thing if you dropped the return
>> value.
>
> I like the proposed idea of having two separate types for handling
> unannotated CF APIs and MRC which would also nicely resolve this
> issue.
>
> Would a separate type for MRC also fall under this proposal or would
> that require a separate proposal?

Considering that we're just RFC'ing here, we can certainly talk about
that.

> And speaking of a separate type for MRC, how about `ManagedReference`
> as a name? Seems much better than `Unmanaged`, nicely contrasts with
> `UnsafeReference` and `ManuallyManagedReference` is a bit of a
> mouthful...

I think we want “managed” to mean “managed for you,” not “managed by
you.”  It's also quite unsafe because you can overrelease it, etc., so
it would have to have “unsafe” in the name somewhere I think.


>>> I don't think this use case even needs to be described in the
>>> documentation for `UnsafeReference` and it's fine if its use is
>>> very much discouraged.
>>> 
>>> Personally I prefer the proposed
>>> `manuallyRetain()`/`manuallyRelease()` over plain
>>> `retain()`/`release()` as it clearly separates the returning and
>>> more generally applicable `release()` from the MRC
>>> methods. `retain()` would probably also have to return the object
>>> which would interfere with the max safe usage pattern.
>> 
>> I don't understand your last sentence; care to clarify?
>
> My main reason for preferring `manuallyRetain()`/`manuallyRelease()`
> over `retain()`/`release()` would be that the former would *not*
> return the object, thus more cleanly separating them from the current
> `release()` which returns the object to be used from now on, with the
> `UnsafeReference` to be discarded at that point.
>
> I just think it might be more confusing to also use `release()` for
> MRC and also introducing `retain()` would only exacerbate the
> issue. For symmetry reasons `retain()` would likely also return the
> object. 

There might be other reasons to do it, but I don't think symmetry is
necessarily a design goal here.

> That would make it very similar to `release()` and `.object` which it
> really shouldn't be as it shouldn't ever be used for handling object
> from unannotated CF APIs.
>
> I think having a third method/property with a very similar signature
> would likely confusion regarding the "Maximally Safe Usage" pattern
> you described.
>
> But as mentioned above I would actually prefer having two separate
> types which would also make this a non-issue.

Questions:

1. How would these types interact?  Does one need to be able to convert
   between them liberally, or is it sufficient to use strong references
   as the common currency?

2. Do you really want a type at all?  Why not just retain() and
   release() as free functions?

>>> As Joe mentioned, `Unmanaged` has a use for manual ref counting
>>> beyond immediate transfer from un-annotated APIs.
>>> 
>>> I have used it for performance reasons myself (~ twice) and while I
>>> think it's a pretty small use case there isn't really any
>>> alternative.
>>> If it would help I can also describe my use-cases in more detail.
>> 
>> Yes please!
>
> One place I used Unmanaged is in a small project where I experiment
> with binary heaps in Swift. I've put the project on Github
> --(https://github.com/Jnosh/SwiftBinaryHeapExperiments) but basically
> I'm using `Unmanaged` in two places here:
>
> 1) Testing the 'overhead' of (A)RC.
> Basically comparing the performance of using ARC-managed objects in
> the heaps vs. using 'unmanaged' objects. In Swift 1.2 the difference
> was still ~2x but with Swift 2+ it's likely approaching the cost of
> the retain/release when entering and exiting the collection.
>
> Now this could also be accomplished using `unowned(unsafe)` but
> `Unmanaged` has some minor advantages:
> 	a) I can keep the objects alive without keeping them in a
> separate collection. Not a big issue here since I'm doing that anyway
> but I also find that `Unmanaged` makes it clearer that & how the
> objects are (partly) manually managed.
> 	b) I had previously experimented with using `unowned(unsafe)`
> for this purpose but found that `Unmanaged` performed better. However,
> that was in a more complex example and in the Swift 1.2 era. A quick
> test indicates that in this case and with Swift 2.1 `unowned(unsafe)`
> and `Unmanaged` perform about equally.

They should.  unowned(unsafe) var T is essentially just an
UnsafePointer.  unowned/unowned(safe) do incur reference-counting cost
in exchange for their safety.

> 2) A (object only) binary heap that uses `Unmanaged` internally
> Not much practical use either in this case since the compiler seems to
> do quite well by itself but still a somewhat interesting exercise.
> `Unmanaged` is pretty much required here to make CoW work by manually
> retaining the objects.

It's hard for me to imagine why that would be the case.  Would I have
needed to use Unmanaged in implementing Arrays of objects, if it were?

> The other project was a simple 2D sprite engine (think a simplified
> version of SpriteKit) I experimented with about a year ago.
> Textures and Shaders were abstracted as value types privately backed
> by reference types that managed the underlying OpenGL objects,
> i.e. destroy the OpenGL texture object on deinit, etc...
>
> I found this to be quite nice to use but ARC overhead during batching
> & rendering amounted to something like 20-30% of CPU time IIRC. (This
> was under Swift 1.2 and with WMO). Using `Unmanaged` was one of the
> things I played around with to get around this and it worked very
> well.

Another case where you can use unowned(unsafe), is it not?

> The `Unmanaged` instances were created when draw commands are
> submitted to the renderer so they were only used inside the rendering
> pipeline.
> I eventually switched to using the OpenGL names (i.e. UInts) directly
> inside the renderer since they are already available anyway but that
> also requires extra logic to ensure the resources are not destroyed
> prematurely (e.g. retaining the object until the end of the frame or
> delaying the cleanup of the OpenGL resources until the end of the
> frame, ...). In many ways it's quite a bit messier than just using
> `Unmanaged`.

I don't see how Unmanaged could have been less messy; don't you still
need a strong reference somewhere to ensure the lifetime?

> I don't think these are particularly great examples and I could
> certainly live without 'native' MRC but ultimately I think it's an
> interesting capability so I'd like to keep it around. 
> Although I'd be in favor of keeping it out of the stdlib but I don't
> think that's really an option just yet...
>
> It would also be interesting to be able to do the same with indirect
> enum instances and closures but it's not like I have a particular use
> case for that ;-)

I don't understand what you might be hinting at here.

-- 
-Dave


More information about the swift-evolution mailing list