43 comments

[ 2.7 ms ] story [ 104 ms ] thread
> If software development is about reducing mental strain on a programmer, then garbage collection is something that goes a long way to that.

Go's GC has its own caveats that you have to watch out for. It doesn't really save you from having to think about your object graph or architecture design.

This is true of GC in all languages.

Many a time I've seen people shrug off issues as "GC will handle it" in C# projects. Usually accompanied with the sound of many a developer crying whilst trying to work out what has gone wrong in production when 256Gb of RAM across several nodes disappears instantly. Memory ballooning, long GC pauses, reference leaks, swapping, VM over commitment are all waiting to hang you.

Can you link to any article about this? I haven't been able to find a good read on this subject.
if you have a very large slice of Structs and then you end up with only a pointer to one of the Structs, the entire slice will be retained. This is also true for slices of Byte.

For non-gophers, a slice is roughly a bounds-checked Vector.

I get the idea, but I am wondering more on what kind of code will cause too much data to be retained and how to code around it.

What you just described is how memory is allocated, but can this cause it to be retained outside a function body?

A typical use case in Java is/was (1) reading a huge file line by line or parsing it as XML, and filtering it. Java's substring is like go's slices in that it is constant-time, but retains the entire string. Consequently, your code that filters out 1‰ of a 1GB XML file may keep hold of the entire file's content.

In Java, you code around it by doing

   new String( line.substring( x, y);
String's constructor will copy the characters to a new buffer. I guess go will have something similar. Problem, of course, is that that means copying the objects. That can especially be an issue if the objects are mutable.

An alternative in Java is to do

    s = s.intern();
But that comes with its own problems. 'intern()' decreases memory usage, but is slow, and library writers cannot know whether it is a good idea to use it or not because they cannot know what is more important to the code calling the library: speed or memory.

(1) Oracle is going to change/has changed this after measuring its impact. It turns out that the two extra fields that each string needs to keep track of what part of a buffer contains its characters more or less compensate for the decrease in memory usage that sharing strings brings.

Interning had other problems. Until some of the more recent JVMs, interning strings was permenant. The memory could never be reclaimed until you restarted the JVM. It was basically a programmer defined memory leak.

Why? My understanding is that when you interned a string you were given something like a constant token to reference it. This did two things:

1. Equality was trivial. Instead of taking to strings and comparing each character, it could just see if the constants were equal. For strings you know you'll see often this could be great source of speedups. 2. You could end up saving a ton of memory. Let's say that string you're interning is 250 character long. Instead of taking up 250 unicode characters plus overhead, it's now... let's say... 8 bytes. Maybe you saved a few hundred bytes. Now multiply a few hundred bytes by a few fields times 10 million records in memory and that's nothing to sneeze at.

Interning is definitely optimization and not part of normal Java development.

I thought the GC point was kind of interesting seeing as Objective C does/did have Garbage Collection (if you're an OS X developer). It's been deprecated and ARC is the replacement. There's been a bit of discussion about this in the past. Maybe if the first iPhone had come with the same hardware we're looking at today the story would be different but I tend to agree with the reasoning behind not supporting GC on iDevices. I feel like the hoops (some types of) Game developers have to jump through on Android even with todays hardware is validation of Apple's reasoning.

http://www.cocoabuilder.com/archive/xcode/322876-why-arc-ove...

http://daringfireball.net/linked/2011/09/19/the-unfamiliar

http://stackoverflow.com/questions/2484079/how-can-i-avoid-g...

Apple failed to develop a stable GC for Objective-C, there were lots of issues when linking with libraries that did not make use of it.

Plus the underlying C part, didn't allow for more than a plain conservative GC.

ARC works by generating the retain/release calls the developers would write anyway with NeXTStep/Cocoa framework.

So while it is a good solution for Objective-C, it isn't because it thrumps GC, but rather due to Apple's failure with a stable GC.

I would argue a step further and say that it's because Apple chose the wrong solution for an OOP language with C-compatibility. They could have invented a language more like C# for NeXTSTEP, but they chose the route with easier C compatibility. Now they're paying the cost of taking that early shortcut.
It goes both ways - they're still reaping the benefits of making it very easy to interface with low-level native code, too (plus sidestepping GC variability).
They still can. All of their competitors are constantly researching new languages but we never hear a peep of any of that from Apple. Presumably the design-driven culture prevents big projects like a new language from ever getting anywhere.
They're making pretty significant advances with Clang and LLVM and have pretty much caught up to GCC in performance and surpassed it in usability. So they're not doing nothing, for sure. But they're pouring all that effort into Objective-C, which will still has the inherent problem of C compatibility. Granted it's evolving (i.e @import), but it can only go so far without breaking compatibility completely like C# did.
I'm talking about language design. Clang and LLVM are not languages (well, not user-written languages).
So then I think we're on the same page: Apple's putting their money into just tooling rather than language design plus tooling.
Given the development timeframe for NeXTSTEP, I don't think that a managed language would have been a likely choice. Remember, this is when your average barn burner workstation came with a 33MHz 386 Class CPU and 4-8MB of memory. The NeXT Computer came with a 25MHz '030 and 8MB of memory.
They could, as Xerox PARC machines were not as powerful and offered:

- Lisp OS

- Smalltalk OS

- Mesa/Cedar (a GC enabled systems programming)

The failure to develop a stable and performant GC is part of the reason. Another part is that such a GC is very difficult for C/Objective-C (one could say impossible or close enough to it as to not make a difference).

However, that's not all of it. The iOS teams wouldn't touch the GC with a 10 foot pole, even had it worked, and with good reason. As the parent poster pointed out, GCs make performance and memory consumption unpredictable, especially on memory-constrained devices.

And yes, phones are memory-constrained, despite the 1/2 - 1GB of memory, because you are also managing some potentially very large assets (retina class images, ...). Especially without virtual memory, where having an unexpected spike in memory consumption can get you killed, having unpredictable memory consumption is a non-starter...and the pain you have to go through on Android to avoid GC problems is significant (for example real-time OCR via the video camera overlaid onto the video stream).

It's interesting that Android appears to be moving away from JIT to AOT with libart, I am very curious as to how that works out. My guess is that at least a subset of developers, those with stringent requirements, would also welcome more predictable memory management solution.

True, although WP doesn't not seem to have the performance issues of Android.

WP7 was full .NET with JIT.

WP8 is WinRT/.NET with AOT compilation.

Both seem to perform much better than Android and on par with iOS.

Although I do conceded that on WP8 there is a mix of reference counting(WinRT) and GC (.NET).

Google was supposed to present more information about ART at FOSDEM, but they canceled the presentation on the last minute. So still no information what they plan there, besides AOSP source code.

Xerox PARC systems already had GC enabled programming languages (Cedar/Lisp/Smalltalk) and they surely were less powerful than today's phones.

Before any insightful discussion, may I suggest that we do not discuss generics?
I might have been opposed to your suggestion, except recently one of the devs explained that they've been discussing it internally [1]. He also said they're not really willing to open the discussion up to the public, for practical reasons. So we're basically getting all we can ask for at this point.

[1]: https://news.ycombinator.com/item?id=7222557

Let me guess, you're worried that the Objective C team will get swamped with requests for generics by developers who behave as if it is impossible to develop software without the feature.

Never happen. The HN community is far too mature for that.

Generic levity is currently available in Go right now.
The lack of generics in Objective-C is one of my biggest complains with the language. A huge portion of the runtime errors I run into are due to containers not containing the type of object I expected them to.
No need to! ObjC developers have soldiered on for years and years without such fanciness.
This. ObjC devs have always been self-selected to not care about static typing, especially in collections.
I think the difference is that Objective-C is dynamically typed from the start, while Go tries to be statically typed first, with the added ability to work with those types dynamically. The latter leads to a lot of additional and arguably ugly code that I expect scares a lot of people off.
Great article! I'd like to see a part 2 to this article where you go a bit more in depth into some differences, with examples of both golang code and objectiv-c code. Thanks!
Could Go be used to replace Java on Android eventually? If he's already comparing it to Objective-C, and since it already has garbage collection, it might not be a terrible idea, and it should make it easier for new programmers wanting to develop for Android, since Go is an easier language to learn than Java and much less verbose.
I think that's more in the hands of Google themselves but there is a project which patches the Go tools and runtime to enable Android apps to interface directly with a shared library written in Go.

https://github.com/eliasnaur/goandroid

Most of Android is written in Java, so no, that's not going to happen in our lifetimes. All we can hope is that ART is good enough that Dalvik goes away and we can start building stable languages on top of ART (as opposed to with Dalvik which only has immature / experimental languages not-named-Java.
> Most of Android is written in Java, so no, that's not going to happen in our lifetimes.

It could easily happen if Google wanted it to. Java can call native code (and vice-versa). Other system vendors have changed core implementations before. Apple has pulled it off 2 or 3 times at least.

It wouldn't be easy, but it's certainly possible.

Yeah, absolutely, but they'd have to have a very good reason to do so. I want them to do it too, but I doubt it will happen unless they reach some major roadblock that makes ART unsuitable.
I don't feel like I've learned much about Go from this article.

    make(chan bool, 1)
what is that 1 for? Why is it not using constructor sytnax shown earlier? (`Chan{bool}` or `BoolChan{1}`?)

`go function` is nice, but in ObjC there's `dispatch_async()` and `NSOperationQueue`. Go does some magic with green threads, segmented stacks and whatnot, but the article doesn't elaborate.

And for an Objective-C developer Go's lack of proper interoperability with Cocoa is disappointing:

http://stackoverflow.com/questions/6322194/cocoa-bindings-fo...

BTW:

   (CGRect){0, 0, 320, 480}
is valid in ObjC (doesn't apply to classes though).
As for (CGRect){0, 0, 320, 480}, it is indeed valid, but it's a lot nicer to have a consistent syntax instead of mixing C with Objective-C.
There is no mixing. Objective-C is a superset of C, therefore C syntax is part of the Objective-C language.
Mixing in the sense that ABAddressBook is a C based API, with no Cocoa wrapper, like how CGRect is a struct, not an class.
As an Objective-C developer, I simply don't think Go solves the same problems as Objective-C – comparing the two seems very apples and oranges to me. If I ever start using Go, it won't be as a direct replacement for Objective-C.

I don't mean to attack the author (I enjoyed the article) but most of Go's "advantages" are also disadvantages when used in an Objective-C/Cocoa environment.

First heading from the article: "Not traditionally object-oriented"

The problem with this is that much of Objective-C is focussed on the AppKit/UIKit view-hierarchy. As the Rust developers noticed when trying to implement the HTML DOM in their Servo project, large hierarchies of similar entities are cumbersome and require repeating yourself often if you don't have access to traditional subclasses. Rust eventually added single inheritance (despite earlier pushing for a purely Go-like compositional interface approach).

Compositional interfaces are a good thing... but so is data and behavioral inheritance. Taking one of these things away makes the language poorer, not richer. Yes, compositional interfaces would be good in Objective-C but a lack of inheritance in Go isn't a perfect world either.

Second heading from the article: "Type inference"

For the mostpart, this is a point in Go's favor. Although the author might not realise that with ARC in Objective-C you can actually just duck type if you want (type as 'id') and the ARC compiler will do type inference behind your back to track memory so you still get all the compile-time checks and other type inference advantages

Third heading from the article: "Garbage collecting"

Ignoring the technical problems with Apple's GC implementation for Objective-C, there were three real reasons why garbage collection sucked badly: (1) it used too much RAM (standard GC drawback), (2) it was slow (because it used too much RAM – it was otherwise efficient), but most critically: (3) it removed deterministic deletion which is critical in Cocoa.

Cocoa relies on its destructors to perform actual work. With GC, you suddenly can't rely on destructors being invoked when you need them. The idea that GC "reduc[es] mental strain" does not apply if instead of transparent lifecycle management with ARC, you suddenly need completely manual "dispose" methods everywhere.

Fourth heading from the article: "Native concurrency"

Apple's approach to native concurrency is that you should use libdispatch. It's slightly more verbose than prefixing a function with "go" but it handles all that go's built-in concurrency handles and actually much more. I think, from its exclusion, that the author is unfamiliar with libdispatch. I've not seen evidence that go has any built in advantages over libdispatch.

Fifth heading from the article: "Packages"

On namespaces: yes, they allow for ways around conflicts. But there is a double edged sword here: names in different namespaces are going to conflict. If you've used a large library like .NET, you'll know what I mean: lots of different classes in different namespaces conflict so that you can't simply say "using XXX" for each namespace and instead you have to reference the whole path. Instead of NSTimer you have to use System.Timers.Timer or System.Threading.Timer – a two letter prefix is now fourteen or seventeen letters.

Also... it is really annoying attempting to follow sample code when the sample code omits all the namespace inclusions. You're trying to learn the API from the code but the code doesn't explain what API it's using forcing you to trial and error hunt around for the correct namespace.

Just gonna leave a short, and not too in depth reply.

OO: Go is great for building server-side software in a fashion where it responds to events. I think it'd be interesting to explore a framework built like that, the way ReactiveCocoa sits on top of Cocoa.

Type Inference: Like you said, id makes everything dynamic in essence, which loses the value of Objective-C being static type at compile time.

GC: My approach tends to be that the compiler/runtime are smarter than me, so I'd rather depend on it than me.

Concurrency: Definitely have used libdispatch (all the time...), excluding it is merely an oversight. I was more focusing on channels, and the go keyword is necessary to introduce beforehand for the example.

Packages: I believe that if there is no conflict, you can just reference the class name, though I may be thinking of another language. I can see your point for sure though.

Thanks for the feedback, would love to hear more of your thoughts on the matter.

> GC: My approach tends to be that the compiler/runtime are smarter than me, so I'd rather depend on it than me.

How is reference counting not leaving it up to the runtime?

> you can actually just duck type if you want (type as 'id') and the ARC compiler will do type inference behind your back to track memory so you still get all the compile-time checks and other type inference advantages

You lose compile-time type checking with id, so it's not at all like type inference. I'm not sure how ARC is relevant at all, as it changed nothing about how id works. Using id everywhere is pretty similar to using void * everywhere in C.

(comment deleted)