Developer

Global variables vs method calls

During my rewrite of Mac AlphaBaby, I’ve been trying to do things the “right way”. That has meant a lot of restructuring of code, trying to reuse classes where possible, and general consolidation. I’ve learned a lot since I first started writing Mac AlphaBaby back in 2003 and I want to bring the code base up to more modern standards.

One of the things I’ve often wondered about in Objective-C is the cost for doing things the “right” way. Every time I make 3 method calls (at least!) for something that might have been a single structure access or global variable access in a C program of yesteryear, I cringe a bit. I know that computers have gotten so much faster that it doesn’t really matter how many virtual or dynamic method calls I’m making for something that isn’t in a speed critical path. However, the part of me that learned C and debugged embedded programs in assembly to optimize for speed still didn’t feel great about all that extravagant method calling.

The current case in question is when creating a string whose value needs to be created a runtime, but then is constant after that, is better off being accessed via a global variable or by some kind of method call on a class. Here’s my test class, with all different ways to access a string:

//  Globaler.h
#import <Foundation/Foundation.h>

extern NSString *globalString;

@interface Globaler : NSObject
{
    NSString    *myInstanceString;
    NSString    *propertyString;
}

@property (nonatomic, retain) NSString *propertyString;

+ (NSString *)staticString;
+ (Globaler *)sharedGlobaler;
- (NSString *)instanceString;

@end

static NSString    *myStaticString;

NSString *globalString;

@implementation Globaler

@synthesize propertyString;

+ (void)initialize
{
    myStaticString = [NSString stringWithFormat:@"Hello %@ world",
            @"static"];
    [myStaticString retain];
    globalString = [NSString stringWithFormat:@"Hello %@ world",
        @"global"];
    [globalString retain];
}

+ (NSString *)staticString
{
    return myStaticString;
}

+ (Globaler *)sharedGlobaler
{
    static Globaler    *theGlobaler = nil;
    if (theGlobaler == nil) {
        theGlobaler = [[Globaler alloc] init];
    }
    return theGlobaler;
}

- (NSString *)instanceString
{
    return myInstanceString;
}

- (id)init
{
    self = [super init];
    if (self != nil) {
        myInstanceString = [NSString stringWithFormat:@"Hello %@ world",
            @"instance"];
        [myInstanceString retain];
        self.propertyString = [NSString stringWithFormat:@"Hello %@ world",
            @"property"];
    }
    return self;
}

@end

And here’s the code which accesses the strings in different ways:

NSString    *refString = @"reference string";

- (void)dealloc
{
    [super dealloc];
}

#define NUM_ITERS    100000000

- (BOOL)doStuffWithString:(NSString *)str
{
    if (str == refString) {
        return YES;
    }
    return NO;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
    NSString     *str;
    Globaler    *g;
    NSTimeInterval    tStart, tEnd;
    int    i;
    
    g = [Globaler sharedGlobaler];
    tStart = [NSDate timeIntervalSinceReferenceDate];
    for (i = 0; i < NUM_ITERS; i++) {
        str = globalString;
        if ([self doStuffWithString:str]) {
            break;
        }
    }
    tEnd = [NSDate timeIntervalSinceReferenceDate];
    NSLog(@"global variable time: %f: %f", tEnd - tStart,
        ((tEnd - tStart) / (NUM_ITERS * 1.0) * 1000.0));
    
    tStart = [NSDate timeIntervalSinceReferenceDate];
    for (i = 0; i < NUM_ITERS; i++) {
        str = [Globaler staticString];
        if ([self doStuffWithString:str]) {
            break;
        }
    }
    tEnd = [NSDate timeIntervalSinceReferenceDate];
    NSLog(@"static variable time: %f", tEnd - tStart);
    

    tStart = [NSDate timeIntervalSinceReferenceDate];
    for (i = 0; i < NUM_ITERS; i++) {
        str = [g instanceString];
        if ([self doStuffWithString:str]) {
            break;
        }
    }
    tEnd = [NSDate timeIntervalSinceReferenceDate];
    NSLog(@"instance variable time: %f", tEnd - tStart);

    tStart = [NSDate timeIntervalSinceReferenceDate];
    for (i = 0; i < NUM_ITERS; i++) {
        str = g.propertyString;
        if ([self doStuffWithString:str]) {
            break;
        }
    }
    tEnd = [NSDate timeIntervalSinceReferenceDate];
    NSLog(@"property variable time: %f", tEnd - tStart);

    tStart = [NSDate timeIntervalSinceReferenceDate];
    for (i = 0; i < NUM_ITERS; i++) {
        str = [Globaler sharedGlobaler].propertyString;
        if ([self doStuffWithString:str]) {
            break;
        }
    }
    tEnd = [NSDate timeIntervalSinceReferenceDate];
    NSLog(@"shared property variable time: %f", tEnd - tStart);

}

What did I find? About what I expected to find. The raw global variable access is the fastest, and it pretty much gets slower as you go on down the line. But, calling a static function which returns a pre-computed static variable came in second. This has some advantages: a relatively small amount of characters to type, you can initialize the static string in the +(void)initialize method, so it only gets created when needed, and it can be put into a more general utility-type class, which lets you group such variables into named collections (named by the class they are in). Here are the timing results:

global variable time: 0.695643
static variable time: 1.075534
instance variable time: 1.268580
property variable time: 1.128956
shared property variable time: 1.496929

The most expensive way to call - using a static method to get a global shared object, whose property we then access, is about twice as slow as the raw global variable. But, the static variable is at least a little closer, and seems just as clean. Bottom line, I had to bump up the iteration count pretty high to even get these numbers (measured in seconds), so all of this stuff is just ridiculously fast, and I probably don’t have to worry about it. But, the low-level tech geek person in me just wanted to know, and it will help me make some design choices (and avoid bad ones for the sake of nonexistent performance concerns).

Error debugging on older iPhone

I decided to use my old iPhone 3G to do some debugging today. However, when I tried to download my app to the device, I got the cryptic error:

Error launching remote program: security policy error.

A relaunch of both the device and XCode failed to clear it up. However, a quick Google search revealed the problem. I had expired profiles on the device (along with their unexpired replacements). Apparently just having the expired profiles on the device was enough to confuse XCode and prevent it from launching the app.

I’m documenting this to myself more as a reminder that if I don’t use a device for testing for a while, looking for expired profiles should be my first step if debugging isn’t working.

Now if only I didn’t have references to iOS 4.0 APIs in my code, perhaps I’d be running on my iOS 3.0-based 3G without any problems!
© 2010-2012 Little Potato Software   Follow littlepotatosw on Twitter        Site Map     Privacy Policy Contact Me