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 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.695643static variable time: 1.075534instance variable time: 1.268580property variable time: 1.128956shared 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).
© 2010-2016 Little Potato Software   Follow littlepotatosw on Twitter        Site Map     Privacy Policy     Contact Me