Global variables vs method calls
11/16/11 02:30 PM Filed in: Mac | Development
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#importextern 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).
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
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).