Concurrent drawRect: - objective-c

I have a large array of objects (typically 500 - 2000) that render to the screen. Unfortunately, rendering is not exactly snappy at the moment.
Each object needs to perform some calculations which take up most of the time and finally draw itself to the screen, i.e. currently my drawRect: method looks essentially like this:
(I've left out trivial optimizations like checking bounding rects vs. dirtyRect for the sake of readability)
- (void)drawRect:(NSRect)dirtyRect
{
for (Thing *thing in [self getThings])
{
[thing prepareForDrawing];
[thing draw];
}
}
An obvious candidate for concurrent processing, right?
I couldn't come up with a good approach to decouple preparation from the actual drawing operations, i.e. perform the pre-processing in parallel and somehow queue the drawing commands until all processing is done, then render all in one go.
However, thinking of the goodness that is GCD I came up with the following scheme.
It kind of sounds OK to me but being new to GCD and before running into weird multi-threading issues four weeks after a public release or just using a bad GCD design pattern in general I thought I'd ask for feedback.
Can anybody see a problem with this approach - potential issues, or a better solution?
- (void)drawRect:(NSRect)dirtyRect
{
[[self getThings] enumerateObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
// prepare concurrently
Thing *thing = (Thing*)obj;
[thing prepareForDrawing];
// always draw in main thread
dispatch_async(dispatch_get_main_queue(), ^{
[thing draw];
});
}
}

That won't work because the invocations of [thing draw] will happen outside of -drawRect: after it has completed. The graphics context will no longer be valid for drawing into that view.
Why are the "things" not prepared in advance? -drawRect: is for drawing, not computation. Any necessary expensive computation should have been done in advance.

Related

Performance of cancelPreviousPerformRequests

I use performSelector:withObject:afterDelay: to delay a layout operation so that it's not done multiple times. Normally, I do something like this (example only):
- (void) setViewNeedsLayout{
if (!_viewsNeedLayout){
_viewsNeedLayout = YES;
[self performSelector:#selector(layoutViews) withObject:nil afterDelay:0];
}
}
- (void) layoutViews{
_viewsNeedLayout = NO;
//layout views
}
It's pretty simple, efficient and effectively delays the layout operation until the end of the current run loop, but it does require that I keep track of the ivar: _viewsNeedLayout.
However, I'm wondering about the performance of cancelPreviousPerformRequestsWithTarget:selector:object:. Instead of using an iVar to keep track of whether I've kicked off the delayed selector, I'm wondering if it might be better to do something like this:
- (void) setViewNeedsLayout{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(layoutViews) object:nil];
[self performSelector:#selector(layoutViews) withObject:nil afterDelay:0];
}
Now, the issue is how performant is cancelPreviousPerformRequestsWithTarget? In some cases, the respective setViewNeedsLayout method (or equivalent) could get called possibly hundreds of times... as an extreme example. But it is an easier way to deal with the delayed/queued method call so I'd prefer to use it if it's not going to have a significant impact on performance.

Creating autoreleased object inside GLKViewController run loop causes malloc error

I'm currently using GLKit to do some OpenGL drawing. I created a normal UIViewController and then added a GLKViewController subclass inside a container to do my drawing. While everything runs fine initially, if I let my program run for a period of time (perhaps 15-30 minutes), eventually it crashes and gives me the following error.
malloc: *** mmap(size=2097152) failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
So I turned on the malloc_error_break breakpoint, and the stack trace points to the following code.
-(NSArray*)meshes:(NSArray *)meshes sortedFromFrontToBack:(BOOL)sortedFromFrontToBack
{
NSMutableArray *sortedMeshes = meshes.mutableCopy;
[sortedMeshes sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
DSNode *mesh1 = obj1;
DSNode *mesh2 = obj2;
GLKVector3 depth1 = isnan(mesh1.boundingSphere.radius) ? mesh1.transformationState.position : mesh1.boundingSphere.center;
GLKVector3 depth2 = isnan(mesh2.boundingSphere.radius) ? mesh2.transformationState.position : mesh2.boundingSphere.center;
GLKMatrix4 mesh1ToCameraSpace = [mesh1 nodeToOtherNodeTransform:self];
GLKMatrix4 mesh2ToCameraSpace = [mesh2 nodeToOtherNodeTransform:self];
GLKVector3 depth1InCameraSpace = GLKMatrix4MultiplyVector3WithTranslation(mesh1ToCameraSpace, depth1);
GLKVector3 depth2InCameraSpace = GLKMatrix4MultiplyVector3WithTranslation(mesh2ToCameraSpace, depth2);
NSNumber *n1 = [NSNumber numberWithFloat:depth1InCameraSpace.z];
NSNumber *n2 = [NSNumber numberWithFloat:depth2InCameraSpace.z]; /* Breakpoint triggered here */
if(sortedFromFrontToBack)
{
return [n2 compare:n1];
}
return [n1 compare:n2];
}];
return sortedMeshes;
}
As I commented, the [NSNumber numberWithFloat:] call throws the malloc error. This method gets called once each frame from my GLKViewController's drawInRect method. Essentially, I have a class which keeps track of my cameras and the meshes which are going to be drawn by OpenGL, and it sorts them in camera space from either front to back for opaque meshes or back to front for transparent before drawing them.
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
glClearColor(self.clearColor.r, self.clearColor.g, self.clearColor.b, self.clearColor.a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DSDirector *director = [DSDirector sharedDirector];
for(DSMesh *mesh in director.opaqueMeshes)
{
[mesh draw];
}
/* The director class keeps track of my scene's cameras and meshes and calls the above method to return the scene's transparent meshes properly sorted */
for(DSMesh *mesh in director.transparentMeshes)
{
[mesh draw];
}
}
From what I've read, the autorelease pool should drain at the end of each run loop, so I wouldn't think that creating a bunch of autoreleased objects every frame is an issue, they should all get flushed each frame. I've profiled my program and checked for any leaks and can't find any, and I'm using ARC as well, which should minimize the risk. When I profile it, the live bytes total never budges, although the overall bytes rises quite quickly, and no leaks are found. In addition, didReceiveMemoryWarning never fires. I'm stumped.
Something smells wrong about the NSNumber creation being the cause of your malloc error. Testing with Instruments might help find the real culprit.
However, you're doing two things you don't need to, so there's some chance that eliminating them might help with your problem.
First, you don't need to wrap floats in an NSNumber to compare them. The basic comparison and mathematical operators work just fine, and don't require extra time or memory for object creation:
if (depth1InCameraSpace.z < depth2InCameraSpace.z)
return NSOrderedAscending;
else if (depth1InCameraSpace.z > depth2InCameraSpace.z)
return NSOrderedDescending;
else
return NSOrderedSame;
Second, the Tile-Based Deferred Rendering strategy implemented by the GPU hardware on iOS devices does its own hidden surface removal optimizations -- sorting opaque geometry front to back is redundant, so all it does is waste CPU time. (There's a decent explanation of this is in the OpenGL ES Hardware Platform Guide for iOS.) You should still sort translucent geometry back to front (and draw it after opaque geometry) for proper blending, though.
So, I think I've figured out what was going on. At some prior point I'd enabled NSZombies and then forgot about it, and so all of my objects that I expected to be deallocated were actually still hanging around. When profiling in Instruments, zombies must not be counted as live, which is why I couldn't figure out why I was seemingly running out of memory when the number of live bytes wasn't climbing. However, when I upgraded to XCode 5, the new memory gauge showed that I was quickly racking up memory, and then when I dropped into Instruments, Leaks was covered up by a warning that said that it would not work properly since NSZombies were enabled. So, I disabled zombies, and now my memory usage is staying constant, just like I expected it to.

UICollectionView locks main thread for ~10 seconds on performBatchUpdates

I have a collectionview with 300 cells, driven by an NSFetchedResultsController. Every so often, all of the objects update, so I receive delegate messages telling me so, and I let the collection view handle the updates as I would a tableview. Unfortunately it locks the main thread for a few seconds every time this happens... I'm not sure why. Here's my code:
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.cascadeViewController.collectionView performBatchUpdates:^{
NSLog(#"performingBatchUpdates");
for (NSDictionary *change in self.changes) {
[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {
NSFetchedResultsChangeType type = [key unsignedIntegerValue];
if (type == NSFetchedResultsChangeInsert) {
[self.cascadeViewController.collectionView insertItemsAtIndexPaths:#[obj]];
} else if (type == NSFetchedResultsChangeDelete) {
[self.cascadeViewController.collectionView deleteItemsAtIndexPaths:#[obj]];
} else if (type == NSFetchedResultsChangeUpdate) {
[self.cascadeViewController.collectionView reloadItemsAtIndexPaths:#[obj]];
} else if (type == NSFetchedResultsChangeMove) {
[self.cascadeViewController.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]];
}
}];
}
NSLog(#"performingBatchUpdates end");
} completion:^(BOOL finished) {
NSLog(#"completion");
// TODO: implement
// [self configureMessageAndFooterView];
}];
NSLog(#"end of method");
[self.changes removeAllObjects];
}
What's going on here? All 300 objects updating at once is not going to happen constantly in my app's real-life execution but enough that I need to worry about it. I'm using a stock UICollectionViewFlowLayout - do I need to do something more custom?
Had the same issue with performBatchUpdates:completion: locking the main thread for seconds in a collection view of just ~100 elements.
After spending way too much time on the issue I found a solution: ensure the cell's size (as returned in -collectionView:layout:sizeForItemAtIndexPath: or defined via the itemSize property of your layout) has not a fractional value. I solved my performance issues by applying floor on the computed height of my cells.
That being said, I have no idea why this happens. By looking at the stack trace of our profiled runs, a lot of time is spent in -[UIViewCollectionViewUpdate _computeGaps], which in turns invokes -[NSArray sortedArrayUsingSelector:] hundreds or even thousands of times (as well as CFSortIndexes, __CFSimpleMergeSortā€¦). By just using an integer value for the height of our cells, sortedArrayUsingSelector is invoked less than 10 times and the whole process completes in a fraction of a second.
I vaguely recall seeing behavior like this before, but I don't have a solution for the NSFetchedResultsController + UICollectionViewFlowLayout combo because we stopped using both of those classes due to a multitude of issues. You might consider checking out the alternatives we open sourced:
TLIndexPathTools as a replacement for NSFetchedResultsController. It provides a TLIndexPathController class that is very similar to NSFetchedResultsController except it also works with plain arrays and it can do animated sorting an filtering (unlike NSFetchedResultsController. There are numerous sample projects, including a Core Data one.
VCollectionViewGridLayout as a replacement for UICollectionViewFlowLayout. It is a uniform, vertical scrolling grid, so it isn't as flexible as UICollectionViewFlowLayout, but the animations are generally much better in most cases it does sticky headers (like UITableView headers). There are a couple of sample projects that let you toggle between UICollectionViewFlowLayout and VCollectionViewGridLayout to see the improvement.
We have an iPad app with a grid-like collection view containing around 1000 items and the above gives us great performance and nice smooth animation as our Core Data database updates in the background.

Is it more efficient to schedule a method to spawn enemies or use the update method of an Enemy cache?

I am using Cocos2d for iPhone and I am wondering if it is more efficient to structure the logic of my code to spawn enemies using this method:
-(void) schedule:(SEL)selector interval:(ccTime)interval
or using the update in an EnemyCache class and verify each time if the time interval is met. Here is the code snippet that is called in the update method of the EnemyCache class (the relative time is an integer value that is updated by the GameScene at each update in the GameScene class - the GameScene update method call is scheduled with an interval of 1 second):
-(void) checkForPlayerCollisionsAndSpwanTime
{
int count = [elements count];
//CCLOG(#"count %i", count);
Element* element;
for(int i=0; i<count;i++){
element = [elements objectAtIndex:i];
NSAssert(element!=nil, #"Nil enemy");
if (element.visible)
{
[element justComeDown];
ShipEntity * ship = [[GameScene sharedGameScene]defaultShip];
CGRect rect = [ship boundingBox];
if (CGRectIntersectsRect([element boundingBox], rect)){
[element doWhatever];
element.visible=FALSE;
[element stopAllActions];
}
}
else{
if(element.spawnTime == relativeTime) {
[self addChild:element];
element.visible=TRUE;
}
}
}
}
The difference is that in this way at each update the checkForPlayerCollisionsAndSpwanTime method goes through the array of enemies. In the first way, via scheduling a selector to call a similar method, I could reduce the time spent by the CPU to look through the array and conditions.
I am not sure how costly is this call:
[self schedule:selector interval:interval repeat:kCCRepeatForever delay:0];
Looking through I see that calls this method (See below) but I wanted to ask in general what is your approach for this problem and whether I should keep using the EnemyCache update method or use the scheduleSelector methods.
-(void) scheduleSelector:(SEL)selector forTarget:(id)target interval:(ccTime)interval paused:(BOOL)paused repeat:(uint) repeat delay:(ccTime) delay
{
NSAssert( selector != nil, #"Argument selector must be non-nil");
NSAssert( target != nil, #"Argument target must be non-nil");
tHashSelectorEntry *element = NULL;
HASH_FIND_INT(hashForSelectors, &target, element);
if( ! element ) {
element = calloc( sizeof( *element ), 1 );
element->target = [target retain];
HASH_ADD_INT( hashForSelectors, target, element );
// Is this the 1st element ? Then set the pause level to all the selectors of this target
element->paused = paused;
} else
NSAssert( element->paused == paused, #"CCScheduler. Trying to schedule a selector with a pause value different than the target");
if( element->timers == nil )
element->timers = ccArrayNew(10);
else
{
for( unsigned int i=0; i< element->timers->num; i++ ) {
CCTimer *timer = element->timers->arr[i];
if( selector == timer->selector ) {
CCLOG(#"CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->interval, interval);
timer->interval = interval;
return;
}
}
ccArrayEnsureExtraCapacity(element->timers, 1);
}
CCTimer *timer = [[CCTimer alloc] initWithTarget:target selector:selector interval:interval repeat:repeat delay:delay];
ccArrayAppendObject(element->timers, timer);
[timer release];
}
Do you have a performance problem in your app? If not, the answer is: it doesn't matter. If you do, did you measure it and did the issue come from the method in question? If not, the answer is: you're looking in the wrong place.
In other words: premature optimization is the root of all evil.
If you still want to know, there's just one way to find out: measure both variants of the code and pick the one that's faster. If the speed difference is minimal (which I suspect it will be), favor the version that's easier for you to work with. There's a different kind of performance you should consider: you, as a human being, reading, understanding, changing code. Code readability and maintainability is way more important than performance in almost all situations.
No one can (or will) look at this amount of code and conclude "Yes, A is definitely about 30-40% faster, use A". If you are concerned about the speed of the method, don't let anyone tell you which is faster. Measure it. It's the only way you can be sure.
The reason is this: programmer's are notorious about making assumptions about code performance. Many times they're wrong, because the language or hardware or understanding of the topic have made big leaps the last time they measured it. But more likely they're going to remember what they've learned because once they've asked a question just like yours, and someone else gave them an answer which they accepted as fact from then on.
But coming back to your specific example: it really doesn't matter. You're much, much, much, much, much more likely to run into performance issues due to rendering too many enemies than the code that determines when to spawn one. And then it really, really, really, really, really doesn't matter if that code is run in a scheduled selector or a scheduled update method that increases a counter every frame. This boils down to being a subjective coding style preference issue a lot more than it is a decision about performance.

Composite NSOperation. Is this a bad idea?

For an iOS4.X application I am working on, we often need to perform an HTTP request, then parse the results, and do something with the results, and so on.
For this I created an NSOperation class to allow for composition of NSOperations using an NSOperation queue. Is there any issue with using NSOperationQueues for small things like this. Some have told me that the queues should be a more permanent thing.
I don't expect the nesting to be more than 2 levels deep in our application.
Here's an example of such usage:
#implementation CompositeOperation
- (id)initWithOperations:(NSArray *)operations {
if ((self = [super init])) {
operations_ = [operations retain];
[[operations_ lastObject] addObserver:self forKeyPath:#"isFinished" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
-(void)dealloc {
[operations_ release];
[operationQueue_ release];
[super dealloc];
}
- (BOOL)isConcurrent {
return YES;
}
#synthesize isExecuting = isExecuting_;
#synthesize isFinished = isFinished_;
#synthesize operations = operations_;
- (void) start {
if (![self isCancelled]) {
operationQueue_ = [[NSOperationQueue alloc] init];
// TODO: Add code to execute this serially
[operationQueue_ addOperations:operations_ waitUntilFinished:NO];
}
}
- (void)cancel {
if (operationQueue_) {
[operationQueue_ cancelAllOperations];
}
[super cancel];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"isFinished"] && object == [operations_ lastObject]) {
[self setIsFinished:YES];
}
}
#end
Thanks,
Mike
I am the one who thinks it is a very good idea so that I even created library after it: CompositeOperations.
There are two operations: simple operation represented by COSimpleOperation object and composite operation represented by COCompositeOperation object.
Simple operation is a smallest possible unit - to quote documentation:
In a nutshell COSimpleOperation is a NSOperation with a small bit of convenience sugar on top of it. As an operational unit for composite operations it usually corresponds to one networking request or some small focused piece of work.
Composite operation is an operation which consists of sub-operations. To quote #mikelikespie:
The point of this object is to make it so one can represent multiple operations that are logically grouped as one operation.
...which is pretty much another description of Composite Design Pattern from Gang of Four Design Patterns.
Composite operation can be parallel or sequential.
Parallel operations are created just as in the code example in question:
NSArray *operations = #[
operation1, operation2, operation3
]; // each operation is NSOperation <COOperation> *
COCompositeOperation *parallelOperation = [[COCompositeOperation alloc] initWithOperations:operations];
To create sequential operation one should instantiate COCompositeOperation with an object conforming to COSequence protocol:
Sequential composition implies sequential flow: sub-operations are executed serially one after another. Sequencing is achieved via collaboration between COCompositeOperation and arbitrary class conforming to COSequence protocol which is used by composite operation as a delegate who decides what operations are and in which order to run them.
To make this composition of operations possible I needed to put small restriction on operations library works with: aside from being NSOperations both COSimpleOperation and COCompositeOperation also conform to <COOperation> protocol:
This conformance basically means that both operations when finished have 3 possible states:
a non-empty result field indicates success
a non-empty error field indicates failure
both empty result and error fields indicate that operation was cancelled from outside (using -[NSOperation cancel] method).
Operation can never have both result and error fields non-empty!
This convention allows Composite Operations to decide at a certain point whether to continue execution of particular group of operations or to stop it. For operations without a specific result [NSNull null] should be passed as result.
For me the rational behind this library was "just" to be able to represent operations so "that they are logically grouped as one operation". There are libraries that achieve the same kind of a higher-level functionality but at the same time they introduce concepts like: Signals in ReactiveCocoa or Promises like in PromiseKit which I don't really need or I would say do not agree with. I wanted something as simple as possible and based on good old well-known NSOperation/NSOperationQueue infrastructure so that's the point of the whole effort.
P.S. I hope this kind of answer fits the SO at least it corresponds exactly to what #mikelikespie was asking about 4 years ago.