Swift 3.0 Unsafe World
technology.meronapps.com
technology.meronapps.com
let a = UnsafeMutablePointer<Int>.allocate(capacity: 1)
a.pointee = 42
FYI, you should never do this.Assignment to the `pointee` is only safe where the `pointee` is already initialized. If the pointee is uninitialized, you should be using `initialize`. e.g.
let a = UnsafeMutablePointer<Int>.allocate(capacity: 1)
a.initialize(to: 42)
While it doesn't actually matter for `Int`, it does matter for classes, unspecialized generics and other Swift reference counted types since Swift will try to release the destination before assigning a new value into it. If the destination is uninitialized garbage, this can cause a crash or other misbehavior. Following proper practice is a good idea – even if you're using a hard coded pure value type like `Int` – since it prevents problems down the road if you decide to change.Similarly, you should `deinitialize` the buffer when you're finished, not simply `deallocate`.
/// Accesses the `Pointee` instance referenced by `self`.
///
/// - Precondition: Either the pointee has been initialized with an
/// instance of type `Pointee`, or `pointee` is being assigned to
/// and `Pointee` is a trivial type.
public var pointee: Pointee
So, `a.pointee = 42` is fine since we all know that `Int` is a trivial type.In this case, `.initialize` is likely also faster because any previous-value-checking logic than might be there is explicitly not used.
You're right that it doesn't mean this is safe in general, but the original comment is wrong that you should never do this.
What previous-value-checking logic are you referring to? I would expect .pointee = 42 to compile down to a single store instruction.
That's a bit too strong. This is safe if the type is trivial, which it was in the article. The author should have added the caveat you wrote, though.
Got distracted one sentence in by an assertion that's so at odds with my understanding of the world.
In summary,
Advantages:
A. Objects are collected immediately when they become garbage.
B. Programming language runtime support is simpler.
C. Memory can be reclaimed easily in programs with distributed heaps.
Disadvantages:
A. Natural race conditions exist in multi-threaded programs when reference counting is used. Atomic read/writes are necessary when incrementing/decrementing the reference count for an object.
B. Cyclic data structures cannot be reclaimed. (This is why you have to jump through hoops to avoid this in Objective-C and Swift.)
C. Every object in your language which is reference counted needs a field to hold the reference count. Or you need a special object in its stead which "points" to your object, like a shared pointer.
D. Pauses with reference counting are still possible. As stated in the book, "When the last reference to the head of a large pointer structure is deleted, reference counting must recursively delete the descendant of the root."
1. Memory high-water-mark is generally lower with RC, which is important for mobile.
2. RC lets you easily determine when there is only one reference to an object. This enables Swift's collections to be value types, one of Swift's key semantics.
It also enables some optimisations e.g. Python has immutable strings, but CPython can take advantage of rc=1 to extend strings in-place (avoiding accidentally quadratic concatenation loops until you start storing intermediate references or you try to run the software on alternate implementations)
For a long time (until the 64b class pointers I think) Objective-C used external tables of reference counts instead of embedding refcounts in objects.
Not really. Obj-C already had a GC and dropped it, to implement ARC because it's faster and more memory efficient.
GC was dropped from Objective-C, because Apple did not manage to make it work safely with C semantics.
Objective-C developers weren't able to mix Frameworks compiled with and without GC, there were lots of corner cases and forum discussions regarding GC runtime crashes were quite common.
Apple's decision in such scenario was to take the alternative route to make the compiler do what the Cocoa manual reference counting patterns already required.
Nothing to do with being faster and more memory efficient, rather having something in Objective-C that would work at all.
However this urban myth keeps being repeated.
There were definitely pain points, including C interop. But these could have been worked through. Apple is good at these sorts of transitions - think x86 or 64 bit, which had the same issues (can't mix 32 bit and 64 bit frameworks, etc).
Probably if Apple had not started making mobile devices, ObjC would have retained GC. But mobile devices had performance requirements that GCs struggle to meet. It's too broad to say that ARC is "faster and more memory efficient"; rather it's more efficient in the ways that are important to Apple. For example, avoiding pause times is more important than total allocation throughput (have to avoid dropped frames). Memory high-water-mark is more important than fragmentation. etc. Servers are the reverse.
Note that GC has been painful on all mobile platforms, not just iOS. That's why Microsoft has taken a step back from GC (WinRT uses ARC). And in Android, GC is one of the most common drivers of performance issues, and avoiding GCs is a key optimization technique. The compiler even warns on allocations inside functions like onDraw().
Android GC has been quite behind the state of the art from what production embedded JVMs like Aonix and Websphere Real Time are capable of. Actually Dalvik was left in limbo for quite awhile before they eventually came up with ART.
So it is easy to just generalize GC vs RC without regarding what algorithms are actually being used, and the qut of said implementations.
There are military weapons control systems and factory robots using real time GC implementations of Java in hardware much more constrainted than a smartphone.
It may be the embedded world has very advanced GCs that would perform well on modern mobile platforms, and just nobody has gotten around to integrating them yet. I don't know! But embedded devices with predictable workload that you can model and test are quite a different beast from a general-purpose computer, so that remains to be proven.
The outcome of Singularity and Midori is a proof of it, in spite of the outcomes prooven by their research, the projects were killed by management.
At least they allowed some of the tech to be repurposed for .NET Native.
>>Has a GC been considered at all? >GC [also] has several huge* disadvantages that are usually glossed over: while it is true that modern GC's can provide high performance, they can only do that when they are granted much more memory than the process is actually using. Generally, unless you give the GC 3-4x more memory than is needed, you’ll get thrashing and incredibly poor performance. Additionally, since the sweep pass touches almost all RAM in the process, they tend to be very power inefficient (leading to reduced battery life). I’m personally not interested in requiring a model that requires us to throw away a ton of perfectly good RAM to get an “simpler" programming model - particularly on that adds so many tradeoffs.*
So it's hardly "completely wrong", especially for iOS.
Referencing counting seems to be in vogue these days because it is somewhat more predictable on mobile systems, at least this is what I heard about the Metro/Store/UWP API at Microsoft (C# is still garbage collected, but the APIs are primarily ref counted).
If you search for Ext-VOS on Don Syme blog, it resembles quite a lot the genesis of .NET before they went with the CLR.
In a system where you control the allocations and deallocations, you have finer control but it's easier to forget to deallocate memory no longer in use. This is easier to track down, but a headache none the less.
For example, in Cedar, Modula-3, Active Oberon, D you can mark a pointer as not being under GC control.
Other GC enabled systems programming languages offer similar feature.
Not necessarily, you could use (and guarantee) a non-moving GC. Moving is but one of the attributes/details you can use for your GC.
There are probably many ways you could design the partition; for instance, I would probably try to design a safe API that was patterned closely after the underlying one (Posix, OpenGL, etc); others would prefer to design more Swift-ian APIs that still expose all the useful capabilities of the underlying libraries.