First of all, "no garbage collection" Technically, referencing counting is garbage collection, it's automatic memory management by definition.
Is ARC really a win over non-refcounted GC + escape analysis? Full manual control over stack vs heap allocations and lifetime, I get, especially for games. But reference counting is not deterministic, and can also generate pauses.
What LLVM static analysis can be done to figure out the lifecycle of objects also applies to other GC algorithms.
ARC may be less likely to get memory paused on release, but ARC doesn't compact free memory, and therefore fragmentation can cause object allocations (malloc) to become more expensive, possibly leading to pauses.
If you use non-copying GC that doesn't compact (ARC doesn't compact), then there are low pause GC algorithms out there that give pretty good bounds (e.g. 5ms pause).
I'd like to see actual benchmarks of both ARC and say, mark-and-sweep on a mobile device rather than speculation and opinion, and both benchmarks must get the same static compilation treatment. That is, if escape analysis tells you that the lifetime of the object is bounded by the current stack frame, then allocate it on the stack, and don't penalize the non-reference-counted GC by allocating 100% of everything on the heap.
ARC may be slower than a good GC, but what it has is deterministic behaviour. A 10ms pause could be tolerable if you know when it will happen.
A fair few years ago I worked in the murky world of J2ME games, and a common pattern was byte[] _variables = new byte[1024] so you could ensure there were no GC pauses.
> A fair few years ago I worked in the murky world of J2ME games, and a common pattern was byte[] _variables = new byte[1024] so you could ensure there were no GC pauses.
That's not the same as ARC, though. The Java equivalent to ARC would be something like having an array of volatile reference counts to go with your variables and fiddling with those at most accesses.
Not the same, but they had the same problem - GC pauses when it likes, not when you like. If you know calling move_hero() takes 23ms, 15 of which is ARC you can plan for that. You can't plan for move_hero() taking 7ms, except when GC happens.
Depending on VM, Java developers can plan where the pauses happen. One way they do that is by object pooling, another is by using DirectBuffers/off-head memory. A third way to do it is with scoped heaps.
It's not like tons of games haven't been shipped with non ref-count GC. Minecraft being the most famous, but games on Unity3D/Mono can have non-ref count GC. Lua is shipped in tons of game engines and uses classic GC.
Regardless of whether you are using automatic GC, or if you are using malloc/free, you can't write a high performance game without carefully working around memory issues. Even C/C++ games like Max Payne have shipped with frame hiccups caused by poor malloc/free behavior that had to be fixed with custom allocators.
If you're writing a game, you have to pay attention to what you're doing. But do non-framrate limited games and regular apps need ref-count GC? I suggest no, they do not.
> But do non-framrate limited games and regular apps need ref-count GC? I suggest no, they do not.
Practically the first piece of advice usually given when someone asks "how do I make my Android app non-laggy" is to avoid allocation wherever possible; even a single frame drop due to a GC pause when the user is scrolling a list, say, can be noticeable.
Azul C4 uses a custom kernel extension. It's not really a general purpose GC for user-level apps. (There are kernel-extension-free variations of it, but they suffer from reduced throughput over the HotSpot GC.)
That's true, but for a mobile device, you can control the kernel, it it may be, from a user experience point of view, that reducing pause latency is better than high throughput. I dunno, has anyone ever considered using Azul-like tricks on mobile kernels? Does ARM have the required HW to support it efficiently?
Can't cyclic reference be fixed by a garbage compiler occasionally cleaning up those things? I think that's how Python does it, not that Python is a beacon of performance, but it's possible.
@Override is optional in Java. All it does is validate that a superclass method with the same signature exists. It doesn't work in the other direction.
This doesn't compile with Swift:
class A {
func foo() {
}
}
class B : A {
func foo() {
}
}
In Java, you make the function part of an interface to achieve that:
interface Foo {
void foo();
}
class A implements Foo { ... }
class B extends A { ... }
If someone renames Foo then A and B fails to compile. If you don't want to use an interface, you do it like this:
class A {
abstract void foo();
}
class B {
void foo() { ... }
}
Yes, it's not exactly equivalent but, this is a rather contrived case that never happens in Java because
a) almost everyone uses @Override because IDEs add it automatically
b) Java programmers don't refactor by using VI and editing a base class's method names. An IDE like IntelliJ IDE does all the work automatically.
c) if you really want to ensure that a class implements a certain interface, you use a Java interface to enforce it.
This swift feature is interesting, but it's not really anything to sell the language.
Yeah it's interesting seeing the contrast in interpretations of "will change the way I program". In one world you have go which aims to truly solve one of tomorrow's biggest programming problems (concurrency), while btw never experiencing this override "bug" since it doesn't allow subclasses and circumvents entire classes of issues altogether, and on the other hand you have Swift which seems to address some rather minor qualms (IMO) with a drastic syntax change.
Swift provides high-level concurrency as well; it just does so via Grand Central Dispatch rather than any built-in language primitives. (While there are no intrinsic language features comparable to Go's channels, I was told that part of the reason for the new trailing closure syntax was so that GCD invocations would look more 'natural'.)
I don't think Swift is trying to sell itself based on the 'override' modifier, overflow-checked arithmetic, Unicode variable names, or any of the other minor features which people keep on talking about. It has a far more capable type system than Objective-C ever had. Its pattern matching is far more than "C's switch statement, but without automatic fall-through". Except for backwards-compatibility, it de-emphasizes or eschews the many at-runtime dynamic features that Objective-C had (e.g. creating and modifying classes and methods dynamically). It has tuples. It's a huge departure from Objective-C, not just incremental improvements packaged into a new language (not that it invented these new features, of course), so it's doing more than just addressing 'minor qualms'.
Okay, can someone who understands this explain it a little better. My understanding is that ARC (or automatic reference counting) wasn't garbage collection because there isn't any garbage collection. I had thought that ARC was a compiler optimization that would analyze your code and add the deconstructor code while compiling based off some possible usage graph or whatnot. ARC therefore is not garbage collection. Yes, it's memory management, but to link the two as the same is wholly inaccurate. Please chime in to let me know how wrong I am.
This is not how ARC works. ARC keeps a count (at runtime) of the number of references to dynamically allocated objects. Every time you create a new reference to an object, the count is incremented, and every time a reference goes out of scope, the count is decremented. When the count reaches zero, the memory is either freed immediately or marked as free-able for later.
Your bit about compile-time code analysis to automatically insert the cleanup code when data is no longer reachable is actually pretty similar to what Rust does today with owned pointers. The downside is that this doesn't work with "normal" code -- you need certain annotations for the compiler to be able to perform this task correctly. In Rust this is done via region pointers (lifetime parameters) and borrow checking.
The canonical text on garbage collection by probably the most respected person in the field considers reference counting (automatic or otherwise) to be GC (http://gchandbook.org/contents.html). I know that's appealing to authority, but what else can you use to prove the definition of a term? Also see [1], which argues these algorithms are all the same thing anyway.
[1] D. F. Bacon, P. Cheng, and V. T. Rajan, “A Unified Theory of Garbage Collection,” presented at the Proceedings of the 19th Conference on Object-Oriented Programming, Systems, Languages & Applications (OOPSLA), 2004.
While we can acknowledge that the more expansive definition of "garbage collection" includes ARC, we can still simultaneously hold another definition of "gc" to not include ARC when discussing Apple & Swift. That's the way Apple documentation and most others are using that term.
Apple docs:
"Garbage collection is deprecated in OS X Mountain Lion v10.8, and will be removed in a future version of OS X. Automatic Reference Counting is the recommended replacement technology."[1]
If we don't use the more common understanding of gc, Apple's documentation doesn't make sense. If we do a substitution of Apple's verbage using ARC==GC, we get nonsense such as:
"Garbage collection (which includes ARC) is deprecated in OS X Mountain Lion v10.8, and will be removed in a future version of OS X. Automatic Reference Counting (which is also part of garbage collection) is the recommended replacement technology."
With the insistence on ARC==GC, we'd have to parse that sentence as garbage collection is being removed and replaced with garbage collection.
It would be nice if we not redefine the meaning of computer terms that have be understood for decades. I know it's semantics, but I take the definitions I learned 20 years ago in my college comp-sci textbooks as the agreed upon definition.
Others would take the definitions They learned 10 or 50 years ago in their college comp-sci textbooks as the agreed upon definition, and those differ.
For example, what I learned decades ago doesn't include that 2004 paper that convincingly shows reference counting to be one end of a scale that has pure GC at the other end.
Yet, that paper made me realize that reference counting is, in some sense, garbage collection.
On the other hand, I see no big problem in having an ambiguous term. That happen all over the world, also in science. Chemists have 'alcohol' (ethanol) vs 'an alcohol' (a family of compounds that includes ethanol), mathematicians have words such as 'algebra', biologists have roses as a family of plants and as a subset thereof, etc.
Before ARC they used manual reference counting. At runtime each object has a count of how many things have declared they have a reference to it. You can increment that count by doing [obj retain] whenever you know you're keeping a reference to it that will outlive the stack frame. When you're done with the object you can do [obj release] to decrement the counter. When the counter hits zero, the memory for the object can be freed (I don't actually know if it's done immediately or added to a list of objects to free later).
What the automatic part of ARC does is, at compile time, analyse the code and automatically add the retain and release messages. If it determines the reference will outlive the stack frame, it adds a retain message. if it determines the reference can no longer be dereferenced (maybe because another object reference is assigned to that variable) it adds a release message.
The runtime characteristics are the same as ARC, it just means the programmer no longer needs to work out where to add the retain and release messages (though they still need to think about reference cycles).
It's boring to discuss if we can't make distinctions. Linear types are also reference counted; the reference count is always either 0 or 1 and it can be computed statically.
Sure, but use appropriate terminology. Automatic memory management has been synonymous with garbage collection for decades. ARC is a form of AMM/GC. So don't say Objective-C+ARC isn't garbage collection, because it is.
The proper distinction would be between local reference counting and GC at the time of release, and global heap traversal from roots.
>First of all, "no garbage collection" Technically, referencing counting is garbage collection, it's automatic memory management by definition.
Thanks for making this post, I have been meaning to say something similar for a while now. And, re: one of your other posts in this thread, the GC handbook is excellent. One of my favorite textbooks.
What most people on this site call "garbage collection" would be more accurately called "tracing garbage collection."
That's kind of irrelevant. The only thing that really matters since swift is built to natively support Cocoa, is was this possible in Objective-C (and to a lesser extent C and C++).
Anybody interested in an ebook about using all of Swift's strict typing features for better app development?
I may be in the process of writing one of those. It may be up to 100 pages so far with only a chapter or two remaining. It may be available for release in a day or two.
Swift seems to be a very pragmatic language. It borrow and learn heavily from other languages, but it doesn't try to be promise anything big. It just introduce opinionated defaults which is safe and tends to be at least 80% of the implementor's intend, while providing alternative for the 20%.
An example is the switch case fall through behaviour of C. In Swift, by default, switch case no longer fall through, but if you need that behaviour, you can always add fallthrough keyword at the end.
When you think about the above behaviour, it seems so much more natural, and wonder why we have been keeping ourselves bitten by this C language construct and developing muscle memory to prevent it by having break, sometimes with a scope (All cases in a switch scare the same scope within C switch, if I don't remember wrongly).
Part of the switch example is solved in other languages (default intends and scoping), of course. But the subtlety of the fallthrough keyword gave me a strong impression. It felt that it should always be a opt-in all these while, rather than an opt-out as it have always been.
With my limited command of english, I feels that I would do it little justice to the language trying to explains some of its concepts like optionals & mutability hints. Some of these concepts only modify the existing solution only so slightly, but meaningful enough to be impactful.
It also doesn't take a stance in the OO and functional debates. It is both OO and functional. Staying as pragmatic as possible.
Finally, Swift is like a wolf hiding under the sheep's skin. Its a very very strongly typed language. It is very specific about it type. But half the time, you can ignore the type and write it like python or ruby. The magic was type inference, its kinda weird but I like that.
You can almost feel that Chris Lattner might have felt he is reaching the point of diminishing return while optimising LLVM for Objective-C. after all, this is a very old language. He done a great job all these while with LLVM but sometimes, the problem is something deeper. Swift is kinda like Objective-C 3.0 without the backward compatibility.
(P.S. Objective-C is my favourite language before Swift)
If programming language is a sword, Swift is just another sword, albeit one that's very sharp.
haha these answers are cracking me up. Who cares? Do people develop iPhone apps using Go? Swift never claimed to invent new paradigms that didn't exist in other languages, it simply brought them to Cocoa.
My comment was specific to lxcid's comment and his surprise to see all these features in a language. I don't understand why my reply got downvoted.
> Who cares? Do people develop iPhone apps using Go?
There is no built-in support for people to start caring in the first place anyway. But Go fundamentally is better suited for server-side code rather than applications as Swift is supposed to.
I do not understand that, either. Also, I find it weird that dictionaries are value types. If I started programming in Swift, I think I would soon have a generic class named 'Dict' that has a single field storing a Dictionary and the same interface as the Dictionary struct.
Does anybody know what is the reason they chose those non-standard behavior for the standard containers?
Isn't that such that you can declare a dictionary or array as constant by using 'let' in the declaration? I think the designers wanted to avoid complicated annotations for constant the way it works in C++. Being able to use 'let' also seems to make collections available for matching in case statements. I recommend watching this[1] tutorial - the way switch/case works in Swift was really impressive to me.
My guess is that they want you to declare map/array arguments as "inout" to be able to add/change things so that the changes are visible by caller. It might not be a bad idea after all.
That makes some, but not much sense to me. Firstly, that is not what I read in the book "The Swift Programming Language":
"Whenever you assign a Dictionary instance to a constant or variable, or pass a Dictionary instance as an argument to a function or method call, the dictionary is copied at the point that the assignment or call takes place."
Even if I assume that that is an error (they didn't think of inout arguments when writing that), the behavior still doesn't make much sense to me.
Say that I want to do a few things with someObject.someField.someDict. In most languages, I would introduce a helper variable: var items = someObject.someField.someDict and do items[42] = 346; items[423] = 356; etc. That won't work, as it clones the dictionary (shallowly). I find it is just too easy to accidentally clone arrays or dictionaries in Swift.
That's true in the majority of cases. However, the code could be written as it is for performance reasons. If someObject is a field in the current object, that isn't that bad, especially if it also is an instance of a nested class.
Also, weirdly, that isn't something the language forbids. It doesn't even make it possible for library writers to prevent it.
I still think we will see some language changes before the official release here in this area because naive users will run into too many weird issues with the current behavior.
For example, if the goal is to have normal function arguments immutable, I think it would be more natural to forbid functions from calling mutating methods on their arguments, unless they are specified as inout.
On the other hand, good compiler warnings might be enough for preventing programmers from accidentally copying containers.
Well, the problem with C++/Java and the ML language families is that they are quite incompatible, so it's not really trivial to mix them. Many languages have tried, most recent languages really. Rust is C with ADTs or ML without garbage collection (not yet sure how closures will turn out), Scala is Java with first-class functions and ADTs, Java and C# are absorbing more and more features from ML, ...
Swift is just another attempt to mix the two, quite a successful one IMO. They kept some warts, probably to maintain binary compatibility with Obj-C, but otherwise I think it's a huge improvement over Java-style languages.
One particular pain point between Java-style and ML-style languages are generics; Java and C# have a common object protocol and thus allow a top type, Object, and run-time type information. So, they must decide whether generics/type parameters are only relevant at compile-time (Java) or are kept at run-time (C#). ML avoids this issue by not having any runtime type information (and no type-testing construct). I haven't yet found where Swift stands, but it's a hard problem to solve.
Swift doesn't have a designated base class like Java, C#, or Objective-C's NSObject. It seems, though, that there are a bunch of interop considerations which means that you might need your Swift classes to inherit from NSObject for them to work properly with Cocoa code.
Swift also doesn't perform type erasure. You can do something like the following, as I believe is possible in C#:
struct SquareMatrix<T> {
let dimension: Int
var backingArray: Array<T>
init(dimension d: Int, initialValue: T) {
dimension = d
backingArray = T[](count:d*d, repeatedValue:initialValue)
}
subscript(row: Int, col: Int) -> T {
get {
return backingArray[row*dimension + col]
}
set {
backingArray[row*dimension + col] = newValue
}
}
}
You have some nice functional stuff. You can define a function on a primitive type like an integer. This enables you to have code like 400.times(doX). doX is a function. Functions as first class citizens are nice.
If you have a function as a final argument, you can change this: something(a, b, { }) to something(a, b) { }. Basically, you just put your anonymous function on the outside of the parenthesis.
I think the book's example was something along the lines of sort(arr) { $1 > $2 }. I think that $1 is some kind of default specifier for the first param and $2 for the second, but I'm not sure.
If you zoom out far enough, few (for example, from a few miles away with my eyes almost closed, I would put Algol and C in the same family: procedural and with block scoping).
There are quite a few distinctly other languages, though. To name a few: APL, COBOL, Forth, sed, Snobol.
> At its core, the language is designed to eliminate bugs, but not in the academic way that, say, Haskell eliminates bugs by preventing normal people from writing code in it
The author is short-sighted. He doesn't know any descent modern OO language, but tries to review Swift.
Except ARC and syntax differences, it is more like a subset of F# or Scala
> Override functions must use the `override` keyword. Now when you rename the superclass's function, all the subclass functions fail to compile because they're not overriding anything. Why did no one think of this before?
As I understand it, Java has a similar `@Override` annotation:
Java's @Override is not only optional, but fails to address an important use case: when you don't want to override anything with your method, and then the base class has a method added to it with the same name as yours. (Or if you simply didn't know that the base class had that method.) Swift will make sure you don't inadvertently override base class functionality by requiring you to use it.
In Java, you'd just add a 'final' keyword to the base method and you'd get a compile time error if someone accidentally tried to override it.
If you want the equivalent of !@Override, a) you can do it with Java8 annotation types and a checker or b) you can just declare the method private.
I'm sure Swift's version also fails to address all of the edge cases people might want to catch. No type system can be complete and catch every use case.
You can't always do b) since your object has to, well, do something. Personally, I'd like to make inheritance strictly opt-in (as in, classes marked final per default), if anything this may be enough to make some people think twice about the let's-use-inheritance-to-share-code antipattern.
That is completely irrelevant to this articles point of illustrating Objective-C bugs that are fixed by Swift. Nowhere did it claim that no other language had solutions for @Override
Is ARC really a win over non-refcounted GC + escape analysis? Full manual control over stack vs heap allocations and lifetime, I get, especially for games. But reference counting is not deterministic, and can also generate pauses.
@Override has been in Java since Java 5.