I was working on Project Euler #22, and got my solution in about 9.6ms. Here’s what I have:
#import <Foundation/Foundation.h>
NSUInteger valueOfName(NSString *name) {
NSUInteger sum = 0;
for (int i = 0; i < [name length]; i++) {
unichar character = [name characterAtIndex:i];
sum += (character - 64);
}
return sum;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
NSMutableString *names = [NSMutableString stringWithContentsOfFile:[@"~/Documents/Developer/Project Euler/Problem22/names.txt" stringByExpandingTildeInPath] encoding:NSASCIIStringEncoding error:nil];
CFAbsoluteTime diskIOTime = CFAbsoluteTimeGetCurrent();
[names replaceOccurrencesOfString:@"\"" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [names length])];
NSArray *namesArray = [names componentsSeparatedByString:@","];
namesArray = [namesArray sortedArrayUsingSelector:@selector(compare:)];
// Marker 1
int totalScore = 0;
for (int i = 0; i < [namesArray count]; i++) {
NSString *name = namesArray[i];
NSUInteger sum = valueOfName(name);
NSUInteger position = i + 1;
totalScore += (sum * position);
}
// Marker 2
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
double timeDiff = (endTime - currentTime) * 1000;
printf("Total score: %d\n", totalScore);
printf("Disk IO Time: %fms\tTime: %fms\n", ((diskIOTime - currentTime) * 1000), timeDiff);
}
return 0;
}
It’s a good time, but I started thinking about how I could make it faster by using multiple threads. With a quad-core CPU, theoretically I should be able to process a quarter of the names on separate threads and then get the total from there. Here’s what I tried (replacing the code between the markers above):
__block int totalScore = 0;
int quarterArray = [namesArray count] /4 ;
typedef void(^WordScoreBlock)(void);
WordScoreBlock block1 = ^{
for (int i = 0; i < quarterArray; i++) {
NSString *name = namesArray[i];
NSUInteger sum = valueOfName(name);
NSUInteger position = i + 1;
totalScore += (sum * position);
}
printf("Total score block 1: %d\n", totalScore);
};
WordScoreBlock block2 = ^{
for (int i = quarterArray; i < (quarterArray * 2); i++) {
NSString *name = namesArray[i];
NSUInteger sum = valueOfName(name);
NSUInteger position = i + 1;
totalScore += (sum * position);
}
};
WordScoreBlock block3 = ^{
for (int i = (quarterArray * 2); i < (quarterArray * 3); i++) {
NSString *name = namesArray[i];
NSUInteger sum = valueOfName(name);
NSUInteger position = i + 1;
totalScore += (sum * position);
}
};
WordScoreBlock block4 = ^{
for (int i = (quarterArray * 3); i < [namesArray count]; i++) {
NSString *name = namesArray[i];
NSUInteger sum = valueOfName(name);
NSUInteger position = i + 1;
totalScore += (sum * position);
}
};
dispatch_queue_t processQueue = dispatch_queue_create("Euler22", NULL);
dispatch_async(processQueue, block1);
dispatch_async(processQueue, block2);
dispatch_async(processQueue, block3);
dispatch_async(processQueue, block4);
However, I’m getting a result of 0, but my times are about a millisecond quicker.
- Is this multi-threaded approach possible?
- If so, how would I implement it?
Do you really want loading the file as part of the timing?
Also, if you want to do them concurrently, you need to use a concurrent queue. You are creating a serial queue, so all the blocks will execute one after the other.
Or, you can call *dispatch_get_global_queue*, and ask for a concurrent queue.
Now, when you add tasks, GCD will farm them out to available worker threads.
Now that the tasks are farmed out, you need to wait for them to complete. This can be accomplished in several ways. If you are using multiple queues, dispatch groups are probably the best approach.
With the same queue though, after all your *dispatch_sync*() calls, you can place a barrier block that will wait until all the previous blocks have completed, and then run…
However, in this case, we are using one queue (though being concurrent, it will execute multiple tasks at the same time… though it pulls the off the queue in the order they were enqueued).
Probably the easiest thing is to use *dispatch_apply*, because it is designed for this exact purpose. You call the same block multiple times, passing in an index. The block gets the index, and you can use that to partition your data array.
EDIT
OK, an attempt at using apply on your specific problem (using your block code as example… I assume it does what you want). Note, I just typed it in (no syntax highlighting here either), so you may need to play with it a bit to get it to compile… but it should give you the general idea).
Hopefully, that makes sense. Let me know if you get it working.
Now, if you ever get into a situation where you really need math performance, you should look into the Accelerate framework. One word. Awesome.