-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Supporting Automatic Reference Counting (ARC) #37
Comments
Xcode seems to do an OK job for simple projects. I imagine this one is a little more complicated than the migrator is designed to handle. That being said, there's nothing stopping anybody from forking and adding ARC support themselves... |
Yeah… I may fork this at some point and see if I can get ARC working nicely. At my work, we're using JSONKit, and we're also anticipating using ARC for future projects. Edit: @AquaGeek, just saw you'd forked this with similar intentions… :) |
XCode doesn't even attempt to refactor JSONKit to be ARC compliant :( |
Yeah, I noticed that as well. There's a lot going on with toll-free bridging with CF objects that the migrator doesn't support without some modifications. I still plan on looking into this more, but it's not going to be an easy fix. |
I know @johnezang is very against ARC and can talk at great length about it :) I think a simple solution is making JSONKit a static library and then you can use it with ARC code or non-ARC code since you can mix and match between targets. |
This README sentence concerns me:
No specific reasons for unsupporting GC are mentioned, so it's hard to know what the reasoning is here. However, I fear there is a misconception about how ARC works. GC is a run-time technology, and results in a notable performance trade-off, as the garbage collector must intermittently pause execution to harvest orphaned objects. Since JSONKit is a performance-focused library, it makes sense to forgo support of GC. ARC is a compile-time technology, which actually results in a notable performance benefit. The compiler generates calls to retain/release/autorelease on your behalf, instead of you inserting them manually. Nothing new happens at run-time, which is why ARC can be enabled/disabled on a file-by-file basis when compiling an Xcode project. JSONKit makes extensive use of Core Foundation classes like CFStringRef; when bridging these to NSString variables some care needs to be taken, but it is not impossible. The code can continue to use CFStringRef. While I can understand avoiding support for GC, I don't think there is a good reason to avoid support for ARC. If there is one, the README file needs clarification. |
It was quick to make it happily build as a static library. I now have it working as a build dependency by dragging the project file into my Xcode project and adding it's product to my Target Dependencies (Build Phases tab), and adding the build product also to 'Link Binary with Libraries'. Is not arc, but it still is fast. ... all that said, bump benzado's comment above mine. I can't discount your your experience or arguments against ARC ... primarily because you don't give us any idea of what they might be. It's possible you have overlooked something, or we could learn something from your experience. Thank you. |
In fact, I think there's another solution. With ARC, following official documentation from LLVM, you can use #ifdef __has_feature(objc_arc) #end |
My two cents : I chose to ship my own https://github.com/groue/GRMustache as a static library. ARC and non-ARC users are happy, and I have not spent a second explaining why the source code is still non-ARC. Tip for @johnezang : the only drawback was that I had to write a custom Makefile, in order to build a single library for several architectures. |
Or you could use iOS Universal Framework which takes care of building for all architectures and packages a nice static framework instead of a static library plus separated header(s). |
With Xcode 4.2, you can specific compiler flags per file. Steps:
|
Yes, "-fno-objc-arc" works fine when you need a one off class in an ARC project (and don't have time or don't want to convert a specific class(es)... but remember that ARC is still running in a reference counted environment so a well written ref counted library is just as good as an ARC library (yes, potentially an ARC version could be a little faster and/or free memory sooner which could reduce max memory requirements)... |
thanks @florentmorin, that works a charm for now. |
In case anyone is wondering.... the short answer is no, ARC will not be supported in JSONKit. The solution is to use the per-file The longer answer is... too long for github.com issue post.. :) But seriously, anyone suggesting that ARC is the best f'ing thing since slice bread and, so help me, better than the invention of the symbolic assembler (no more translating your program by hand in to a bunch of random numbers that you then had to enter in to the machine by flipping individual dip switches... effective bits per second for loading a program this way: < 1 (one) bps..)... you all are just going to stop drinking all that Kool-Aide and come down off that high. Anyone who want's to suggest that ARC is somehow faster or... whatever... better have some honest to god hard core numbers to back it up... numbers that I can replicate myself. Because any claim without the numbers to back it up means you're just making sh*t up. I apologize for being so blunt, but... it's quite clear that a lot of people have not given ARC a lot of critical thought. The fact the official sales pitch (er, documentation) is completely devoid of actual numbers and is pretty vague on the specifics should be a warning sign. The other big warning sign is the fact that you have never had this thought cross your mind: "Man, I'm totally bottlenecked and limited by retain / release overhead.." Because the fact is even if retain / release represents 10% of your programs execution time.... magically (and generously) cutting that in half with ARC is only going to net you about a 5% overall increase (all things being equal and the usual disclaimers). |
"Premature optimization is the root of all evil" -- Knuth (and/or Hoare) The funny thing is I have yet to meet a single person who believes the primary reason for GC or ARC is performance. I can't imagine anyone would. The reason for them is to remove one of the most common sources of memory leaks and programmer error. Except for a handful of limited cases the compiler and/or runtime environment can do the job of managing memory as good (or better than) the engineer. And I for one would be willing to take a pretty sever performance hit for them. Because even a 100% performance hit on memory allocation and deallocation is utterly trivial to everything else going on in the system. Indeed I challenge anyone to give real world numbers that would so that ARC would result in a serious performance loss. It's your library, you can do what you want with it. But I think you are wrong about your reasons. |
Just watched the 2011 WWDC video on ARC and from that it is pretty clear there are cases where ARC and the related compiler optimizations will result in faster code, but there are also cases where it might slow things down. The ReadMe for JSONKit should be updated to reflect that -fno-objc-arc works, if it in fact does. Because at the moment it says:
and yet in the comment above @johnezang says:
Leaves one wondering, "which is it? 'Works just fine' or is 'normatively undefined'". |
Which one is it? Both. ARC is "supposed" to interoperate just fine with non-ARC code, but there is virtually no documentation about the technical details of "how" ARC works that would allow me to say with confidence that there wont be any issues. On the other hand, you can compile JSONKit using the ‡: Caveat Emptor, YMMV, etc. Why is it important to make a distinction between what's officially and unofficially supported? Because JSONKit isn't exactly your run of the mill Objective-C source code, which means it's much more likely to trip over corner cases in the way that ARC "magically" handles reference counting for you behind the scenes. The "official" ARC documentation essentially amounts to "ARC just works! And you should use it!" But when I start to dig in to the details of how all this "magical" automatic reference counting works behind the scenes, I see a lot of things that really concern me. For example, the function
... which just leaves me speechless. I would have loved to sit in on the discussions where the compiler guys signed off on this... "Yea, you need a hard guarantee that the compiler will emit precisely this sequence of bytes from this point forward, no matter what? No f'ing problem. We'll just stick a comment somewhere that all future modifications to the compiler and optimization passes have to make sure they never violate this insignificant little detail." Then there's tiny little details like this and this (from the
Naturally the
Unfortunately, while the above is true for the majority of cases, it's that 0.0001% of the time where it's actually _pretty f'ing important_ that a NSSet objectToReturn = nil;
[lock lock];
objectToReturn = [[arrayProtectedByMutexBarrier objectAtIndex:0] retain];
[lock unlock];
return([objectToReturn autorelease]); |
@haikusw “Works just fine” in combination with that compiler flag should be construed as “works just fine”: the compiler is treating the file with normal retain-release gloves, so the emitted assembly will (or should) be exactly the same as compiling JSONKit in a non-ARC project. |
Wow, my comment was the one that made @johnezang close this issue - I feel honored. :) Nice reply from @johnezang though (thanks) with some excellent specifics to feed nightmares. Fodder for a few Radars I suppose. After a quarter century of programming I have to admit to a level of distrust of "magic! it just works!" nearly as high as yours johnezang. :-P That said, I found WWDC 2011 session 322 "Objective-C Advancements In Depth" to be quite informative about ARC, especially the second half "ARC Internals" by Greg Parker. Has a lot of illustrations of what exactly the compiler inserts into your code and then what the optimizer does to that code. Still clearly a lot of "magic" going on and I predict that at some point I'm going to be chasing bugs down in the "magic" and cursing ARC when that happens… |
@johnezang, I'm curious : could you please explain why the retain/autorelease dance is so important in the lock/unlock example? |
Well, it has a lot to do with what the following means, for some strong, hyper pedantic definition of means. I'm going to make one change to the previous example- Instead of using NSSet *objectToReturn = nil;
[lock lock];
objectToReturn = [ivarProtectedByMutexBarrier retain];
[lock unlock];
return([objectToReturn autorelease]); The question is: What, exactly, does the above mean? And because there is no Objective-C standard (and by standard I mean something like the ANSI C99 standard, not the informal prose description of the language from Apple), it's actually pretty difficult to say with any authority what the above means. And you know what, it's actually kind of important that you be able to say exactly what it means. Like, _really_ important. So, for the purposes of our discussion, I'm going to define Objective-C as "A strict superset extension to the ANSI C99 grammar, where the Objective-C extensions are defined in terms of their equivalent "source to source" ANSI C99 translation." This vastly simplifies things- under this definition, Objective-C is not just a "strict superset of C", it literally is C, the Objective-C extensions are pure syntactic sugar that are there to make your job easier. It also helps that this was the de facto case for nearly 20 years (although in practice, the "source to source" preprocessor was rolled up into Some might take exception to this definition, but without a formal standard... oh wait, that fact that it's even possible to have a serious argument over what the definition of Objective-C is means... I'm right, so there. :) Moving on, using The One True Definition... In ANSI C99, the example above would become something like: NSSet *objectToReturn = NULL;
objc_msgSend(self->lock, sel_registerName("lock"));
objectToReturn = objc_msgSend(self->ivarProtectedByMutexBarrier, sel_registerName("retain"));
objc_msgSend(self->lock, sel_registerName("unlock"));
return(objc_msgSend(objectToReturn, sel_registerName("autorelease"))); ... and Objective-C classes (i.e., typedef struct {
// List of ivars, built by appending the ivars from each subclass.
} NSSet; // The ObjC class name... Again, it's pure syntactic sugar, and until recently, was how things actually worked (witness the now deprecated The C99 standard has quite a lot to say about the above. What tends to catch even experienced C programmers off guard is that the C standard does not require that the statements above by executed in the linear sequence implied by the example. In fact, the C standard doesn't even require any of the statements above to literally be executed. From the C99 standard (section 5.1.2.3, clause 3, 5, and 9):
... in other words, when you flip the optimizer on, it has a lot of freedom as to what it can and can't do, and the C99 standard says it can do a lot of things that you would intuitively think it couldn't. The golden rule, though, is that the optimized code must behave "as-if" it was executed according to the semantics of the C99 abstract machine. In practice most compilers will treat function calls that cross translation units as described in clause 9 above, but this is not strictly required by the standard. So what happens when we "get rid of" NSSet *objectToReturn = NULL;
objc_msgSend(self->lock, sel_registerName("lock"));
objectToReturn = self->ivarProtectedByMutexBarrier;
objc_msgSend(self->lock, sel_registerName("unlock"));
return(objectToReturn); What happens when you turn on the optimizer? Well, it depends on the compiler, but there is a very real possibility that it's going to turn the above in to something like the code below due to constant propagation: objc_msgSend(self->lock, sel_registerName("lock"));
objc_msgSend(self->lock, sel_registerName("unlock"));
return(self->ivarProtectedByMutexBarrier); ... which, when you think about it, looks disturbingly like how you'd be forced to write code using ARC: NSSet *objectToReturn = nil;
[lock lock];
objectToReturn = ivarProtectedByMutexBarrier;
[lock unlock];
return(objectToReturn); ... which the optimizer might just be silently turning in to: [lock lock];
[lock unlock];
return(ivarProtectedByMutexBarrier); ... which is also known as a Race Condition, and are, by far, _the most difficult bugs I have had to deal with._ Think this can't happen? Let's take a look at the Objective-C Garbage Collection implementation... The core primitive, which absolutely everything depends on, made it all the way through testing, shipped in 10.5, and lasted all the way until 10.5.2 before it was caught. I give you __private_extern__ id objc_assign_strongCast_gc(id value, id *slot)
{
objc_strongCast_write_barrier(value, slot);
return (*slot = value);
} That little gem was fixed in objc4-371.1 (along with a number of other GC race condition bugs that were so bad that it was amazing things even ran). _That little GC bastard above consumed three solid weeks of my life, where I spent ten hours a day trying to figure out why that which could not happen by definition was actually happening._ Probably the easiest way to tell if you're getting screwed by ARC behind your back is-
Some indications that there are some fundamental problems: #import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
void *ptr = NULL;
NSString *string = NULL;
if(string == NULL) { abort(); } // Line 8
if(string == (void *)0) { abort(); } // Line 9
if(string == (void *)1-1) { abort(); } // Line 10
if(string == ptr) { abort(); } // Line 12
}
return(0);
} When compiled: x.m:10:18: error: implicit conversion of C pointer type 'void *' to Objective-C pointer type 'NSString *' requires a bridged cast if(string == (void *)1-1) { abort(); } // Line 10 ^~~~~~~~~~~ x.m:10:18: note: use __bridge to convert directly (no change in ownership) x.m:10:18: note: use __bridge_transfer to transfer ownership of a +1 'void *' into ARC x.m:12:18: error: implicit conversion of C pointer type 'void *' to Objective-C pointer type 'NSString *' requires a bridged cast if(string == ptr) { abort(); } // Line 12 ^~~ x.m:12:18: note: use __bridge to convert directly (no change in ownership) x.m:12:18: note: use __bridge_transfer to transfer ownership of a +1 'void *' into ARC 2 errors generated. Lines 8, 9, and 10 are required, by definition, to be equivalent (see page 748). The C99 standard also requires line 12, which compares the value of two pointer variables that have been assigned |
@johnezang Regarding your last point, that "no one really thought through any of the finer points and corner cases" --- the C99 standard has rules about "C pointers" but doesn't cover "Objective-C pointers". It's right there in the compiler error message:
ARC effectively introduces a new type, with new rules, in order to make the behavior of ARC predictable. It's much more than just magically inserting retain/release calls in C code. It's a vast improvement over non-deterministic garbage-collection for precisely this reason. I don't care if you want to continue using retain/release in JSONKit. Since it's a per-file compilation option, it really doesn't matter. You've spent a long time fine-tuning the code for performance, and switching it to use ARC would be a substantial change. I am personally curious to see how it would impact performance, but it is by no means clear right now whether it would be worth the significant investment of time. And you're right, it increases the potential to introduce subtle, infuriating bugs. But, don't go around accusing other engineers who work just as hard and care just as much as you do of being careless, just because you don't know them personally. That's just really lousy. |
thanks @florentmorin, |
I would like to discuss your future plans for supporting Automatic Reference Counting (ARC). I see in commit c2146ff that you added a section to the README in which you state:
Is this the long-term plan? What are your reasons for not supporting ARC?
Note: Edited to remove unsubstantiated claims about ARC, as well as a dumb suggestion for creating an ARC branch of the code. No one wants to maintain two branches for something like this.
The text was updated successfully, but these errors were encountered: