I’m embarking on the most difficult project I’ve written so far, and I have been making extensive use of Instruments, especially the Time Profiler. If you’re like me, you opened up Instruments after downloading Xcode for the first time because it looked cool. Then you shut the computer and cried for a while. But recently, I’ve been noticing that my application tends to be sluggish during some dragging operations, and I wanted to figure out precisely why.
First, let’s talk a little about the project and why it slows down where it does. I’ve been playing a game called EVE Online. One of the core mechanics of this MMO is that your character trains skills 24 hours a day, whether you are online or not. But skills can only be queued if they will start within 24 hours. And certain skills can’t even be trained until other skills have been reached. As a result, people really want help in planning longer skill queues. There’s a really good program for this called EVEMon… which runs only on the PC.
Enter fledgling developer. EVE Online has not been available on the mac for very long, so the options for us are quite slim. I’m hoping to come up with something that works well and eliminates the need for Wine (which doesn’t run EVEMon well) or Bootcamp (which is annoying).
So, I’ve downloaded all of the skill information, set it up in arrays and dictionaries, etc etc. Next I created a class called EVESkillPlan, which works with my EVESkill items. These are my Models (although I’ve integrated some Controller functionality in the EVESkillPlan, which is supposed to be a big no-no – more on this in another post).
Here’s where the Instruments comes into play.
I’ve set up a drag and drop interface. New skills are dragged from the source list on the left to the right:

Dragging into an NSTableView gives you two opportunities to validate the operation, once to give feedback to the user as to whether or not the drop will succeed (tableView:validateDrop:proposedRow:proposedDropOperation) and once to actually tell the data object to follow through with the data manipulation (tableView:acceptDrop:row:dropOperation:). The problem in my app is that the validation operation takes too long, and performance begins to suffer as the drag operation continues.
So what’s causing the slowdown? This is where Instruments is helpful.

It turns out there is an extensive test of the validation method when the application loads character files from dis (or when they are downloaded from the EVE API), as the program attempts to make an EVESkillPlan instance from the skills that the character has already trained. This is probably a really bad way of doing things, as the skills that the character has already trained can be assumed to be valid, because the game won’t allow them to be trained out of order, so the validity test is unnecessary. But if it works well now, I don’t care. In any case, at this point, testing the loading time for the characters can show us where the slowdowns happen. Here’s an example trace as the application ran last week:



4.24s doesn’t seem like a lot of time, but it represents the slowest part of the drag operation. Can we speed it up?
You can use the Time Profiler to determine what is taking the longest amount of time in the app. Previous to today’s improvements, the code stored data in an NSDictionary that contained the EVESkill instance and an NSInteger for the level (skills range from Level 1 to Level 5, and must be trained in order of increasing level). If you look closely at the trace, you’ll find that the lookups for these NSDictionaries takes a considerable amount of time, owing to the fact that the NSDictionary equality test has a number of steps. What if we created a class that uses a faster isEqual: method?
EVESkillPlanItem is a simple class that does just that. It has two properties, an EVESkill instance and a NSInteger for the level of the skill. Equality requires that the EVESkill object and the level number are equal. This cuts out at least one step from each equality statement, since the NSDictionary first makes sure that the two dictionaries have the same number of items. Who needs that when you’re sure that they will? In any case, here’s the trace for the updated code:


Voila, 1.58s. Huge, huge improvement. Notice that the isEqual: method for EVESkillPlanItem is almost three times faster than the corresponding method for NSDictionary.
And I find that dragging is much speedier as well, so the main goal is fixed for the time being. It’s possible there may be more improvements to be made, but I can live with the performance for now. Interesting how much things got better. Time Profiler is your friend!