Obj-C performance week 1: Concatenating strings

Thoughts By 4 years ago

This is the first in a series of blogs on performance tests in Objective C.

This post will compare the time taken to concatenate strings using common NSString and NSMutableString methods. Often I see people using initWithFormat: to paste together all their strings, but is this the fastest?

There are four common ways to do this (see my code at the end for the exact tests):

NSString initWithFormat:

Possibly the most common, initWithFormat: takes a format string and with placeholders that get replaced with your content.

NSString stringByAppendingString:

An NSString method, it will append a string onto an existing string and return the result as a brand new object.

NSMutableString appendString:

Similar to the one above, this appends a string but to itself, a mutable string object.

NSMutableString appendString:, copy

The exact same as above, but making an immutable copy of the result. I’m including this in my testing as most classes will make copies of string objects to ensure it isn’t mutated elsewhere.

The results

As you can see, making a mutable string and appending string to it is the fastest way to concatenate strings. Obviously this will result in a mutable object, but copying the result is the second fastest way according to these tests. stringByAppendingString: seemed to be a lot slower than the rest…

Explanation

Using the time profiler (being a bit blind – not getting the most detailed information on Apple’s internal classes) I can explain why these results are like they are.

Firstly, because stringByAppendingString: returns a new object every time, there is a lot of memory being allocated, which is a waste considering they’re only being used for a short amount of time. Also, all of these objects are being added to the autorelease pool.

The format string methods are a bit slower than the mutable string methods because the format string must be parsed before doing any work, to figure out where the placeholders are and their positions.

Obviously this leaves the fast mutable string methods, being the fastest of the bunch.

The code

If you want to run the same tests, my code is as follows. Done 3 million times to separate each test by mere milliseconds.

#define TEST_FORMAT 1
#define TEST_APPEND_IMMUTABLE 2
#define TEST_APPEND_MUTABLE 3
#define TEST_APPEND_MUTABLE_COPY 4

//	Current test goes in here...
#define TEST TEST_APPEND_IMMUTABLE

for(NSUInteger stringsCount = 2; stringsCount <= 10; stringsCount++)
{
	@autoreleasepool
	{
		NSMutableArray* strings = [[NSMutableArray alloc] init];
		NSString* format = @"";
		
		for(NSUInteger s = 0; s < stringsCount; s++)
		{
			[strings addObject:@"John Smith"];
			format = [format stringByAppendingString:@"%@"];
		}
		
		const CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
		
		for(NSUInteger i = 0; i < 3000000; i++)
		{
#if TEST == TEST_FORMAT
			NSString* result = [[NSString alloc] initWithFormat:format, strings[0], ...];
			
#elif TEST == TEST_APPEND_IMMUTABLE
			NSString* result = @"";
			for(NSUInteger s = 0; s < stringsCount; s++)
				result = [result stringByAppendingString:strings[s]];
			
#elif TEST == TEST_APPEND_MUTABLE || TEST == TEST_APPEND_MUTABLE_COPY
			NSMutableString* result = [[NSMutableString alloc] init];
			for(NSUInteger s = 0; s < stringsCount; s++)
				[result appendString:strings[s]];
			
#if TEST == TEST_APPEND_MUTABLE_COPY
			NSString* immutableResult = [result copy];
#endif
#endif
		}
		
		const CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
		
		NSLog(@"%f seconds", end - start);
	}
}