Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This kind of bragging sound just like "I don't read books".


No, in a lot of circumstances the release / retain worked really well and is really understandable. It is more a point of view from folks who structured their programs in a certain way that made ARC rather unneeded. I look at it more like I know where the resources need to be or I have some very specific rules myself. It is more prevalent where you have some type of document life cycle or specific file interactions.


Does it? My bad.


To you, maybe. But that says more about you than the OP.

The tangible benefits of ARC are at best marginal, and there are downsides, which some can reasonably find more significant, and thus ARC not worthwhile.

First off, "manual" reference counting is misnamed. It is at the very least "semi-automatic" and highly automatable.

So how does a property declaration look with "MRC" vs. ARC?

    @property (nonatomic,strong) NSString *str;
vs.

    @property (nonatomic,strong) NSString *str;

Can you tell which is which?

So how about use?

    someObject.str = @"Hello World!";
vs.

    someObject.str = @"Hello World!";

Can you tell which is which? In use, ARC and MRC are mostly indistinguishable, as long as you use accessors/properties for all instance variable access. You do that, right? Right?

OK, so there is automatic dealloc. That's a nice convenience, but actually not intrinsically tied to ARC. How do I know? I implemented automatic dealloc for a project once. Didn't use it much after because the small effort spent on dealloc just didn't seem worth it, especially as you also had other boilerplate to implement, the coders, isEqual and hash.

The one biggie was weak, which was previously handled via non-retained references, so no automatic nilling. This could actually be a pain from time to time, but really not that huge a deal and it was also never really intrinsic to ARC, as shown by Apple recently adding weak for non-ARC code.

So those are/were the upsides. Not really that much.

Among the downsides is a performance penalty that could be both significant and somewhat unpredictable, on the order of 50%-100% slower in realistic scenarios (in extreme cases it can be an order of magnitude or more). You could also get unexpected crashes because of retain/releases added in unexpected places. We had a crash in a method that did nothing but return 0;

http://blog.metaobject.com/2014/06/compiler-writers-gone-wil...

However, for me the biggest pain point was the semantic changes in ARCed-C: what used to be warnings about an unknown message are now hard compile errors, and that seriously cramps Objective-C's style as a quick-turnaround exploratory language.

And yes, I have worked with/built both significant ARC and non-ARC codebases.

What's really troubling about these things (GC, ARC, Swift) is the rabid fanboyism. When GC came out, you were complete idiot and a luddite if you didn't embrace it and all its numerous warts (which were beautiful) wholeheartedly, and buy into all the BS.

Then when GC was dropped and replaced by ARC: same thing. Did anyone say GC? We meant beautiful shiny ARC, you ignorant luddite. And it quickly became an incontrovertible truth that ARC was faster than "MRC", despite the fact that this wasn't so. And when people asked about it, mentioning they had measured significant slowdowns, they were quickly attacked and everything questioned, because everybody "knew" the "truth". Until a nice gentleman from Apple chimed in an confirmed. Oops.

So please ratchet down the fanboy setting. Technologies have upsides and downsides. Reasonable people can differ on how they weigh the upsides and the downsides.

And of course we are seeing the same, just magnified by an extraordinary amount, with Swift.


What's really troubling about these things (GC, ARC, Swift) is the rabid fanboyism. When GC came out, you were complete idiot and a luddite if you didn't embrace it

I don’t know how to check this, but that isn’t how I remember it at all.

There was a brief flurry of interest when Apple added GC, but it never caught on. If it had been popular, Apple would have kept it.

Then when ARC arrived, it was genuinely popular, and that’s why it was a lasting success. By automating the exact same retain/release/autorelease protocol that people were already doing by hand, it fit much more neatly into existing Obj-C best practices.

I do partly agree with you, that Swift seems to have some problems that haven’t yet been fully resolved, but Apple are still going full steam ahead with it. It feels more like GC, but Apple are treating it like ARC.


> [GC not catching on]

Well, GC was much harder to adopt, as it wasn't incremental. Either your code was GC or not. All of it.

> If it had been popular, Apple would have kept it.

Weeelll...I think the bigger problem with GC was that it didn't work; they never could get all the bugs out. Including the performance issues, but more significantly potentially huge leaks. Well, technically that's also performance.

I also very much liked the idea of ARC, it looked exactly like what I had lobbied for, and it certainly was much better than GC, if more limited (cycles). And then I tried it and noticed (a) for my idiomatic and highly automated use of MRC, the benefits were between minimal and zero and (b) the drawbacks, particularly the stricter compiler errors, were a major PITA, and unnecessarily so.

This becomes noticeable when you write TDD code in an exploratory fashion, because with the errors you have to keep 3 sites in your code up-to-date. That becomes old really fast, but I guess most people don't really do TDD (their loss!), so it's not something that's a major pain point in the community.

Incidentally, someone once mailed me that they had switched some code back from ARC to MRC (partly due to what I'd written), and contrary to their expectations and previous assumptions could confirm that the difference was, in fact, negligible.

> [Swift like ARC when it's more like GC]

That's a good observation. Of course, Swift is a much more dramatic change than even GC ever was, and interestingly the community seems to be much more radical/rabid than Apple. For example, in the community it seems de-rigeur that you must use immutable structs whenever possible, whereas Apple's Swift book gives a few conditions where you might consider structs and then says you should use classes for everything else.


> Apple's Swift book gives a few conditions where you might consider structs and then says you should use classes for everything else

This is an unfortunate edict that the core team is trying to get amended.


> Apple are treating it like ARC

That's because Swift uses ARC instead of traditional tracing garbage collection.


No, that’s completely separate.

What I mean is, ignore the specific technologies: Apple is treating Swift like an ideal incremental improvement (like ARC was) whereas it’s really a major change in direction that may or may not prove to be a good idea (like GC was).


> Can you tell which is which? In use, ARC and MRC are mostly indistinguishable, as long as you use accessors/properties for all instance variable access.

Well, that's the beauty of upgrading to ARC: your declaration syntax doesn't need to change; usually it just means that you can drop a bunch of autoreleases in your codebase.

I read your blog post, where you mention this snippet of code being optimized in an odd way, printing "1 2" in Clang:

  int main() {
    int *p = (int*)malloc(sizeof(int));
    int *q = (int*)realloc(p, sizeof(int));
    *p = 1;
    *q = 2;
    if (p == q)
      printf("%d %d\n", *p, *q);
  }
Of course, when you have odd things like this happen you're reaching down into undefined behavior, and then all bets are off.

Really, I you're just focusing on performance too much and neglecting the fact that ARC isn't there for increasing the performance of your code: it's there so that you can program in a manner that's more safe. Sure, it's easy to manage memory manually, until that one time you double free and SIGSEGV.


> drop a bunch of autoreleases

And add a bunch of allocs, which I find less useful because they don't really communicate intent, whereas the autoreleases usually do. Also, in my code-base, autoreleases are less than 0.5% of the total code, and that includes a lot of legacy code.

In fact, after creating a macro for creating class-side convenience creation methods along with initializers in one go, I could probably drop the use to nearly zero. (The convenience creation methods are always +fooXYZ { return [[[self alloc] initXYZ] autorelease]; } so very automatable).

> undefined behavior

Yes, that's the excuse. It's a bad excuse.

> focusing on performance

Well, performance is my specialty. It also has the advantage of more likely giving you actual data, rather than vague feelings.

> that one time you double free and SIGSEGV

I've not found ARC code to crash less, and if you remember the article, it is about code that cannot possible crash actually crashing due to ARC.


> Yes, that's the excuse. It's a bad excuse.

I'm amazed that you find this to be a bad excuse, since it's what all optimizing compilers rely on to produce performant code.

> I've not found ARC code to crash less, and if you remember the article, it is about code that cannot possible crash actually crashing due to ARC.

The code crashes because you violated an invariant at some point of program execution. The way the C-style languages work, it's legal to crash (or not) at a place that isn't necessarily the place where the undefined behavior happens (interestingly, not only does this have to be after: it can also happen before the buggy code executes, due to reordering, pipelining and such). This is one of the guarantees you get "for free" by using a safer language such as Swift.


> [undefined behavior for optimization]

Yes, I am aware that that is the excuse. It still is a terrible excuse.

> because you violated an invariant at some point of program execution.

Not true. The code in question is a callback, so my code is getting called by Apple code, and ARC dereferences a pointer it has no business de-referencing.

May I remind you that the code that crashed due to a segfault was

    {
       return 0;
    }


Also, if you think "You have violated something, for which we will give you no diagnostic, and therefore we feel free to crash you at some random other place in the program that has nothing to do with the place where the alleged violation took place, again with no diagnostics" is reasonable...well, could I interest you in purchasing a bridge in New York? Or some Nevada oceanfront real estate?

And no, you don't need a "safer" language like Swift, you just need to not go for the crazy modifications the optimizer writers pushed into the C standard.


> if you think "You have violated something, for which we will give you no diagnostic, and therefore we feel free to crash you at some random other place in the program that has nothing to do with the place where the alleged violation took place, again with no diagnostics" is reasonable

No, I generally don't, which is why I use a safer language like Swift most of the time (well, that, and the fact that I can take advantage of a nicer standard library). You put "safer" in quotes, because I don't think you quite understand how compiler optimizations are supposed to work. I think Chris Lattner's three part series, "What Every C Programmer Should Know About Undefined Behavior"[1], is a great explanation from a compiler writer for why dangerous optimizations have to exist. It certainly helped me when I was in a similar place as you, not quite understanding why the optimizer did seemingly stupid things.

Really, the crux of the issue is that every language has tradeoffs: you can program in assembly and know exactly what your program is doing, but you lose the portability and convenience of higher level languages. Then you have the C family of languages, where you get access to some higher level concepts at the cost of ceding control to a compiler. The compiler's job is to generate assembly that matches what you are trying to do in the most efficient way possible. Of course, if it did so too literally it would be very slow to account for every single "stupid" thing you could have done, so there are some general rules that are imposed that you must follow in order for the compiler to do what you want. Then, of course, we have the high-level languages which do account for every stupid thing you might do, and so can provide proper diagnostics.

[1] http://blog.llvm.org/2011/05/what-every-c-programmer-should-...


I have programmed in C since ~1986, so please don't try to explain the language to me, and don't assume that my POV comes from a place of ignorance.

The craziness with undefined behavior is a fairly recent phenomenon. In fact, I started programming in C before there even was a standard, so all behavior was "undefined", yet no compiler manufacturer would have dreamed of taking the liberties that are taken today.

Because they had paying customers.

The actual benefits of the optimizations enabled are fairly minimal, and the cost is insane, with effectively every C program in existence suddenly sprouting crazy behavior, behavior that used to not be there.

Yeah, and while I know Chris personally, like and respect him, I am not taking his word for it.

> The compiler's job is to generate assembly that matches what you are trying to do in the most efficient way possible

Exactly: "matches what you are trying to do". The #1 cardinal rule of optimization is to not alter behavior. That rule has been shattered to little pieces that have now been ground to fine powder.

Sad times.

See: Proebsting's law, "The Death of Optimizing Compilers" and "What every compiler writer should know about programmers or “Optimization” based on undefined behaviour hurts performance"


> I have programmed in C since ~1986, so please don't try to explain the language to me, and don't assume that my POV comes from a place of ignorance.

I apologize for my tone, it was more patronizing that I had intended it to be.

> The craziness with undefined behavior is a fairly recent phenomenon. In fact, I started programming in C before there even was a standard, so all behavior was "undefined", yet no compiler manufacturer would have dreamed of taking the liberties that are taken today.

I feel that the current renewed focus on optimizing compilers has really been born out of the general slowing of Moore's law and stagnation in hardware advances in general, as well as improvements in program analysis taken from other languages. Just my personal guess as to why.

> Exactly: "matches what you are trying to do". The #1 cardinal rule of optimization is to not alter behavior. That rule has been shattered to little pieces that have now been ground to fine powder.

The optimizing compiler has a different opinion than you do of "altering behavior". If you're looking for something that follows what you're doing exactly, write assembly. That's the only way you can guarantee that the code you have is what's being executed. A similar, but not perfect solution is compiling C at -O0, which matches the behavior of older compilers: generate assembly that looks basically like the C code that I wrote, and perform little to no analysis on it. Finally, we have the optimization levels, where the difference is that you are telling the compiler to make your code fast; however, in return, you promise to follow the rules. And if you hold up your side of the bargain, the compiler will hold up its own: make fast code that doesn't alter your program's visible behavior.


> The optimizing compiler has a different opinion than you do of "altering behavior".

Obviously. And let's be clear: the optimizing compilers of today. This rule used to be inviolable, now it's just something to be scoffed at, see:

> If you're looking for something that follows what you're doing exactly, write assembly.

Er, no. Compilers used to be able to do this, with optimizations enabled. That this is no longer the case is a regression. And shifting the blame for this regression to the programmers is victim blaming, aka "you're holding it wrong". And massively counter-productive and downright dangerous. We've had at least one prominent security failure due to the compiler removing a safety check, in code that used to work.

> Finally, we have the optimization levels, where the difference is that you are telling the compiler to make your code fast;

Hey, sure, let's have those levels. But let's clearly distinguish them from normal operations: cc -Osmartass [1]

[1] http://blog.metaobject.com/2014/04/cc-osmartass.html


The article you linked to in your blog post is most likely not serious; it's a tongue-in-cheek parody of optimizing compilers, though one that's written in a way that brings it awfully close to invoking Poe's Law.

But back to the main point: either you can have optimizations, or you can have code that "does what you want", but you can't have both. OK, I lied, you can have a very small compromise where you do simple things like constant folding and keep with the intent of the programmer, and that's O0. That's what you want. But if you want anything more, even simple things like loop vectorization, you'll need to give up this control.

Really, can you blame the compiler? If you had a conditional that had a branch that was provably false, wouldn't you want the compiler to optimize it out? Should the compiler emit code for

  if (false) {
  	// do something
  }
In the security issue you mentioned, that's basically what the compiler's doing: removing a branch that it knows never occurs.


> either you can have optimizations,

> or you can have code that "does what you want",

> but you can't have both.

This is simply not true. And it were horrible if it were true. "Code that does what I want" (or more precisely: what I tell it to) is the very basic requirement of a programming language. If you can't do that, it doesn't matter what else you can do. Go home until you can fulfill the basic requirement.

> very small compromise

This is also not true. The vast majority of the performance gains from optimizations come from fairly simple things, but these are not -O0. After that you run into diminishing returns very quickly. I realize that this sucks for compiler research (which these days seems to be largely optimization research), but please don't take it out on working programmers.

What is true is that you can't have optimizations that dramatically rewrite the code. C is not the language for those types of optimizations. It is the language for assisting the developer in writing fast and predictable code

> even simple things like loop vectorization

I am not at all convinced that loop vectorization is something a C compiler should do automatically. I'd rather have good primitives that allow me to request vectorized computation and a diagnostic telling me how I could get it.

C is not FORTRAN.

As another example: condensing a loop that you can compute the result of at runtime. Again, please tell me about it, rather than leaving it in without comment and "optimizing" it. Yes, I know you're clever, please use that cleverness to help me rather than to show off.

> Really, can you blame the compiler?

Absolutely, I can.

> If you had a conditional that had a branch that was provably false,

"Provable" only by making assumptions that are invalid ("validated" by creative interpretations of standards that have themselves been pushed in that direction).

> wouldn't you want the compiler to optimize it out?

Emphatically: NO. I'd want a diagnostic that tells me that there is dead code, and preferably why you consider it to be dead code. Because if I write code and it turns out to be dead, THAT'S A BUG THAT I WANT TO KNOW ABOUT.

This isn't rocket science.

> security issue you mentioned, that's basically what the compiler's doing: removing a branch that it knows never occurs.

Only for a definition of "knows" (or "never", take your pick) that is so broad/warped as to be unrecognizable, because the branch actually needed to occur and would have occurred had the compiler not removed it!

> The article you linked to in your blog post is most likely not serious

I think I noted that close relationship in the article, though maybe in a way that was a bit too subtle.


Hmm…let's try a simpler question, just so I can get a clearer picture of your opinion: what should the compiler do when I go off the end off an array? Add a check for the bounds? Not put a check and nondeterministically fail based on the the state of the program? How about when you overflow something? Or dereference a dangling pointer?

You seem to not be OK with allowing the compiler to trust the user to not do bad things–but you do trust them enough to out-optimize the compiler. Or am I getting you wrong?




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: