I have been working on tracking down a performance issue in one of our apps. What seems to happen is that sometimes a UIImageView takes seconds to render an image and, because of the way the code is written, this blocks the main thread.
I've tracked down the issue to the fact that the slow images are progressive JPEGs at retina resolutions. For whatever reason, when the file reaches a certain size, decoding the JPEG becomes a very expensive operation.
Anyway, in the course of writing a simple test application, I realized I didn't know how to time how long the draw event takes. It clearly is blocking the main thread, so I decided to just try and time the run loop iteration. Unfortunately, it ended up a bit hackish. Here's the relevant code:
///////////////
//
// This bit is used to time the runloop. I don't know a better way to do this
// but I assume there is... for now... HACK HACK HACK. :-)
buttonTriggeredDate_ = [NSDate date];
[[NSRunLoop mainRunLoop] performSelector:#selector(fire:) target:self argument:[NSNumber numberWithInt:0] order:1 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
///////////////
NSString* path = [[NSBundle mainBundle] pathForResource:imageName ofType:type];
self.imageView.image = [UIImage imageWithContentsOfFile:path];
The callback is as follows (more hackiness!):
- (void)fire:(NSNumber*)counter {
int iterCount = [counter intValue];
NSLog(#"mark %d", iterCount);
NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:buttonTriggeredDate_];
// We really need the second pass through - if it's less than X, assume
// it's just that first runloop iteration before the draw happens. Just wait
// for the next one.
if (iterCount < 1) {
iterCount++;
[[NSRunLoop mainRunLoop] performSelector:#selector(fire:)
target:self
argument:[NSNumber numberWithInt:iterCount]
order:1
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
} else {
self.statusDisplay.text = [NSString stringWithFormat:#"%# - Took %f Seconds",
self.statusDisplay.text,
interv];
}
}
So, my question is, basically, how would you do this? I want to be able to drop in different images and run a benchmark to make sure I have a rough idea of how long it takes to run this. I also would like to have it be reasonably consistent and free of jitter.
Hmm, maybe I should just subclass UIImageView and record times around [super drawRect:frame]?
What would you do?
You're likely trying to time the wrong part of the problem. As you suspect, the problem is most likely in the decode (and probably also in memory allocation and copying). It is not likely that the problem is in the final "drawing" step per se.
This is the kind of problem that Instruments is built for. Fire up Instruments and look for your hotspots.
As a possible solution, if Instruments tells you that decoding is what's blocking you, you may consider rendering the images onto a CALayer on a background thread before putting them into the view.
Related
I'm experiencing something weird with GCD.
First I have a method which performs heavy calculations, and then does some UI layout calculation and updates the UI based on the results.
Without GCD the UI freezes for about 0.5 seconds every time this method is called.
So I went to GCD and did this:
// INIT
// stored in ivar (called only once!)
dispatch_queue_t q = dispatch_queue_create("com.testcompany.myqueue", NULL);
// WORK
dispatch_async(q, ^(void) {
[self performHeavyCalculationAndUpdateUI]; // modifies self.calculationData
});
After this change the method takes about 2-5 seconds until the change appears in the UI.
The work code in -performHeavyCalculationAndUpdateUI: running in the serial queue calls some UI modification code on the main queue (the main thread) the way Robert Ryan suggested here:
dispatch_async(dispatch_get_main_queue(), ^{
// Read ivars and objects used during calculation in the serial queue (problem?)
CalculationData *result = self.calculationData;
// UI updates like [foo addSubview:bar];
});
On the main queue I'm also reading some ivars and objects which were used during calculation in the serial background queue. Can this be a problem?
It still takes about 2-5 seconds until something shows up. Far longer than without GCD.
I'm not using GCD anywhere else except here.
Has anyone else experienced this kind of problems with GCD and knows a solution?
After hours I figured out: The Reason.
The troubles you are describing do not come from GCD at least not in the code you posted. I made a quick test function to log the times required to switch queues:
-(IBAction)beginWork:(id)sender{
NSTimeInterval buttonPushTime = [NSDate timeIntervalSinceReferenceDate];
// Defined as an ivar : dispatch_queue_t q;
// created as "q = dispatch_queue_create("com.testcompany.myqueue", NULL);" in viewDidLoad
dispatch_async(q, ^{
NSTimeInterval backgroundBeginTime = [NSDate timeIntervalSinceReferenceDate];
[NSThread sleepForTimeInterval:5.0];
NSTimeInterval backgroundEndTime = [NSDate timeIntervalSinceReferenceDate];
dispatch_async(dispatch_get_main_queue(), ^{
NSTimeInterval backOnMainThreadTime = [NSDate timeIntervalSinceReferenceDate];
NSLog(#"seconds to start on background thread = %f",backgroundBeginTime-buttonPushTime);
NSLog(#"seconds to perform in background = %f",backgroundEndTime-backgroundBeginTime);
NSLog(#"seconds to get main thread again = %f",backOnMainThreadTime-backgroundEndTime);
NSLog(#"total seconds = %f",backOnMainThreadTime-buttonPushTime);
});
});
}
Then I ran this code on my iPod touch 2nd gen (an arguably very old device. iOS 4.2.1). The results were as follows:
seconds to start on background thread = 0.001747
seconds to perform in background = 5.000142
seconds to get main thread again = 0.000190
total seconds = 5.002079
The queue switching in this case added less than 2 thousandths of a second to the time. I recommend you add some similar logging to find your delay.
BTW, are you performing dispatch_queue_create every time you invoke performHeavyCalculationAndUpdateUI? Are you performing this heavy calculation numerous times? If so, you might want to make sure you're creating the queue only once and dispatching to it as needed. (By separating the queue creation/release from what you dispatch will also help you diagnose what's causing the performance issue ... GDC overhead or some problem in performHeavyCalculationAndUpdateUI. Finally, are you also performing dispatch_release when you're done with the queue?
There's some GCD overhead, but strikes me that judicious use of when you create and release the queue would be prudent and would help in diagnosing the problem, too.
I have a couple labels I am using as a HUD for the player during a game. I am updating these labels frequently, so that the player has up-to-date information. The problem is is that I've been using
uiLabel.text = [NSString stringWithFormat:#"%3.0f", value];
to pass the new value that the label should have. I have noticed, however, that I have something of a soft-memory leak here. As I'm doing this update multiple times a second and this creates a string that is set to autorelease, I'm ending up taking more memory than I need. And keeping it, as the view is not going away.
I also tried to alloc and release strings explicitly, such as:
NSString* value = [[NSString alloc] initWithFormat: #"%3.0f", value];
uiLabel.text = value;
[value release];
However, I find that this seems to cause the same thing, but faster, though I don't know why. In this situation I would have thought there should never be strings sitting around waiting to be released at all, since I'm so explicitly dismissing them.
Can anyone see what I'm doing here that I obviously am failing to see? Is there a better/more preferred way to handle this? Some cursory searching didn't turn up much for me.
You're not doing anything out of the ordinary. Even with:
uiLabel.text = [NSString stringWithFormat:#"%3.0f", value];
the autorelease pool gets drained every time your code returns control to the run loop (so at least as often as you see the UI updating). If you see growing memory allocations, you should look elsewhere.
I have an audio player and I want to show the current time of the the playback. I'm using a custom play class.
The app downloads the mp3 to a file then plays from the file when 5% has been downloaded. I have a progress view update as the file plays and update a label on each call to the progress view. However, this is jerky... sometimes even going backward a digit or two.
I was considering using an NSTimer to smooth things out. I would be fired every second to a method and pass the percentage played figure to the method then update the label.
First, does this seem reasonable?
Second, how do I pass the percentage (a float) over to the target of the timer. Right now I am putting the percent played into a dictionary but this seems less than optimal.
This is what is called update the progress bar:
-(void)updateAudioProgress:(Percentage)percent {
audio = percent;
if (!seekChanging) slider.value = percent;
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] init];
[myDictionary setValue:[NSNumber numberWithFloat:percent] forKey:#"myPercent"];
[NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:#selector(myTimerMethod:)
userInfo:myDictionary
repeats:YES];
[myDictionary release];
}
This is called first after 5 seconds but then updates each time the method is called.
As always, comments and pointers appreciated.
UserInfo is a void *, so you could just pass an int for percentage directly, or just a pointer to your NSNumber object; it doesn't have to be an NSDictionary. Apart from that, using a dictionary is a standard approach, and doesn't have excessive overhead.
Note that you are creating a new timer with a 5 second repeat every time this method is called, which may or may not be your intention. When you create repeating timers (or normal ones) you should make sure to invalidate them when not required.
I'm wondering if there are any best practices for improving UI responsiveness while doing Core Data saves (not fetches) with large collections of managed objects.
The app I'm working on needs to download fairly large amounts of data on set intervals from a web service until complete. On each interval, a batch of data is downloaded, formatted into managed objects, and saved to Core Data. Because this process can sometimes take as long as 5 minutes until fully complete, simply adding a loading screen until everything finishes is not really an option, it takes too long. I'm also interested in doing frequent writes to Core Data, rather than one big write at the end, to keep my memory footprint low. Ideally, I'd like the user to be able to keep using the rest of the application normally, while simultaneously downloading and writing these large data sets to Core Data.
Unfortunately, what seems to be happening is that when I try to save my inserts that I put into the managed object context for each batch, that save operation blocks the user from interacting with the rest of the app (swiping tables, touching buttons, etc) until complete. For those short periods of time where a Core Data save is taking place, the app is very unresponsive.
Naturally, I've tried making those saves smaller by reducing the size of the individual batches that get downloaded per interval, but besides the inconvenience of making the whole process take longer, there will still be instances when a user's swipe is not captured, because at that particular time a core data save was happening. Reducing the size simply makes it less likely that a missed swipe or a missed touch will happen, but they still seem to happen often enough to be inconvenient.
For the inserts themselves, I've tried using two different implementations: insertNewObjectForEntityForName:inManagedObjectContext as well as setValuesForKeysWithDictionary. Both exhibit the problem I described above.
I tried prototyping a much simpler test to see performance in both the simulator and on the device, I've provided the important elements here. This example doesn't actually download anything from the web, but just writes a whole bunch of stuff to core data on set intervals from within a TableViewController. I'd love to know if anyone has any suggestions to improve responsiveness.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(doTimerWork:) userInfo:nil repeats:YES];
}
-(void) doTimerWork:(id)sender
{
for (int i = 0; i < 1000; i++)
{
Misc * m = (Misc*)[NSEntityDescription insertNewObjectForEntityForName:#"Misc" inManagedObjectContext:managedObjectContext];
m.someDate = [NSDate date];
m.someString = #"ASDASDASD";
m.someOtherString = #"BLAH BLAH BLAH";
m.someNumber = [NSNumber numberWithInt:5];
m.someOtherNumber = [NSNumber numberWithInt:99];
m.someOtherDate = [NSDate date];
}
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"Experienced an error while saving to CoreData");
}
}
Typically you would download your data on a background thread and insert/update managed objects into its managed object context.
On the main thread you would register and receive the NSManagedObjectContextWillSaveNotification and use mergeChangesFromContextDidSaveNotification: to update the main managed object context.
Is this what you are doing?
Also, read Multi Threading with Core-Data.
It sounds like you need to throw your data intensive stuff with Core Data onto a separate thread, which is fortunately pretty easy in Cocoa. You can just do:
[obj performSelectorInBackground: #selector(method:) withObject: arg];
And then design things so that once that data intensive operation is finished, call:
[otherObject performSelectorOnMainThread: #selector(dataStuffIsDone:) withObject: arg waitUntilDone: NO];
At which point you can update your UI.
The main thing to remember is to always keep your UI logic on the main thread, for both proper design, and because very odd things can happen if you do anything with UIKit from a different thread, since it isn't designed to be thread safe.
I am programming a game on the iPhone. I am currently using NSTimer to trigger my game update/render. The problem with this is that (after profiling) I appear to lose a lot of time between updates/renders and this seems to be mostly to do with the time interval that I plug into NSTimer.
So my question is what is the best alternative to using NSTimer?
One alternative per answer please.
You can get a better performance with threads, try something like this:
- (void) gameLoop
{
while (running)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self renderFrame];
[pool release];
}
}
- (void) startLoop
{
running = YES;
#ifdef THREADED_ANIMATION
[NSThread detachNewThreadSelector:#selector(gameLoop)
toTarget:self withObject:nil];
#else
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f/60
target:self selector:#selector(renderFrame) userInfo:nil repeats:YES];
#endif
}
- (void) stopLoop
{
[timer invalidate];
running = NO;
}
In the renderFrame method You prepare the framebuffer, draw frame and present the framebuffer on screen. (P.S. There is a great article on various types of game loops and their pros and cons.)
I don't know about the iPhone in particular, but I may still be able to help:
Instead of simply plugging in a fixed delay at the end of the loop, use the following:
Determine a refresh interval that you would be happy with and that is larger than a single pass through your main loop.
At the start of the loop, take a current timestamp of whatever resolution you have available and store it.
At the end of the loop, take another timestamp, and determine the elapsed time since the last timestamp (initialize this before the loop).
sleep/delay for the difference between your ideal frame time and the already elapsed time this for the frame.
At the next frame, you can even try to compensate for inaccuracies in the sleep interval by comparing to the timestamp at the start of the previous loop. Store the difference and add/subtract it from the sleep interval at the end of this loop (sleep/delay can go too long OR too short).
You might want to have an alert mechanism that lets you know if you're timing is too tight(i,e, if your sleep time after all the compensating is less than 0, which would mean you're taking more time to process than your frame rate allows). The effect will be that your game slows down. For extra points, you may want to simplify rendering for a while, if you detect this happening, until you have enough spare capacity again.
Use the CADisplayLink, you can find how in the OpenGL ES template project provided in XCODE (create a project starting from this template and give a look to the EAGLView class, this example is based on open GL, but you can use CADisplayLink only for other kind of games