FROM : Graham Cox
DATE : Fri May 02 17:30:51 2008
OK, I have managed to implement this after a lot of poring over the
docs. I'm not sure if it's the most efficient way to actually handle
the thread communication, but it does work (using NSMachPort). As I
hoped, there isn't a big problem with drawing the graphics as they
should be, apart from a bit of occasional blank output when the window
is resized - BUT, performance is terrible. Worse than synchronous
drawing by a long shot - I haven't measured it because it's really
obvious how much the drawing lags the user - must be an order of
magnitude slower.
Question is of course, why? The thread is calling the exact same
drawing code as the view normally does, so that's a constant. So it
must be down to the thread communication/overhead. Or perhaps just my
minimal understanding of threads, locks, ports etc. ;-)
When I need to draw, I flag that fact to the class (the class is
handling all views using a single secondary thread, but in fact I only
have tried it so far with one), and the class bundles the update rect
and view into an NSInvocation, which is placed in a queue. An
NSPortMessage is used to wake up the thread which pulls the invocation
off the queue and invokes it, which does the drawing. Maybe the use of
NSInvocation is slow? Not sure why it should be.
Here's the pertinent parts of the code - have I done something silly?
(all vars starting with 's' are static globals):
+ (void) setDrawUsingSecondaryThread:(BOOL) threaded
{
if ( threaded && !sThreadedDrawing )
{
// create the thread and a port for it
NSPort* port = [NSMachPort port];
[port setDelegate:self];
// create a queue for the drawing invocations that are received -
this is done to maintain order when
// several views may be needing update.
sDrawingThreadInvocationQueue = [[NSMutableArray alloc] init];
// create a lock used to mediate access to the queue
sQueueLock = [[NSLock alloc] init];
// start the thread
[NSThread
detachNewThreadSelector:@selector(secondaryThreadEntryPoint:)
toTarget:self withObject:port];
sThreadedDrawing = YES;
sDrawingThreadPort = port;
}
sDrawingThreadShouldRun = threaded;
}
+ (void) secondaryThreadEntryPoint:(NSPort*) port
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* loop = [NSRunLoop currentRunLoop];
[loop addPort:port forMode:NSDefaultRunLoopMode];
NSDate* endDate = [NSDate distantFuture];
do
{
[loop runMode:NSDefaultRunLoopMode beforeDate:endDate];
}
while([self updateThreadShouldRun]);
[loop removePort:port forMode:NSDefaultRunLoopMode];
[port invalidate];
[pool drain];
}
+ (void) signalSecondaryThreadShouldDrawInRect:(NSRect) rect withView:
(NSView*) aView
{
if([self drawUsingSecondaryThread])
{
NSInvocation* invocation = [NSInvocation
invocationWithMethodSignature:[aView
methodSignatureForSelector:@selector(drawContentInRect:)]];
[invocation setTarget:aView];
[invocation setSelector:@selector(drawContentInRect:)];
[invocation setArgument:&rect atIndex:2];
// queue the invocation for processing by the thread
[sQueueLock lock];
[sDrawingThreadInvocationQueue insertObject:invocation atIndex:0];
[sQueueLock unlock];
// tell the thread to process the queue:
NSPortMessage* pm = [[NSPortMessage alloc]
initWithSendPort:sDrawingThreadPort receivePort:nil components:nil];
[pm setMsgid:1234];
[pm sendBeforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[pm release];
}
}
+ (void) handlePortMessage:(NSPortMessage*) portMessage
{
// receive the update request on the secondary thread via the port
message
unsigned msg = [portMessage msgid];
if( msg == 1234 && [sQueueLock tryLock])
{
NSInvocation* inv = [[sDrawingThreadInvocationQueue lastObject]
retain];
if ( inv != nil )
{
[sDrawingThreadInvocationQueue removeObject:inv];
[sQueueLock unlock];
NSAutoreleasePool* pool = [NSAutoreleasePool new];
NSView* aView = [inv target];
if( aView != nil && [aView isKindOfClass:[NSView class]])
{
if([aView lockFocusIfCanDraw])
{
[inv invoke];
[[aView window] flushWindow];
[aView unlockFocus];
}
}
[pool drain];
[inv release];
}
else
[sQueueLock unlock];
}
}
On 2 May 2008, at 9:07 pm, Graham Cox wrote:
>
> On 2 May 2008, at 6:13 pm, Kyle Sluder wrote:
>> As for spawning multiple threads, you want your drawing to be
>> performed really quickly. I would strongly advocate keeping a thread
>> around for the life of your view and having it sit an a loop that
>> ends
>> with it performing a blocking read on some IPC port. That way it
>> gets
>> scheduled off the processor but you don't suffer the thread-creation
>> or -destruction penalty every time you perform a draw.
>
> From what you're saying, I should have a worker thread that waits
> for a request to draw, do the drawing, then go back to sleep until
> next time. What isn't clear is if this thread should be per-view, or
> used by all views that work this way, and how exactly it should be
> flagged to do the drawing (and all the sleep issues, etc). I'm not
> sure what an IPC port is (I will look it up) but I get the general
> idea. If you can point me in the right direction to answer some of
> these, I'm happy to experiment with what's needed on the drawing
> side of things (and I'm hoping it's relatively little different from
> the single-threaded case).
>
> So my question at this stage isn't about drawing but about setting
> up and controlling the worker thread, (I think).
>>
>> Drawing from a secondary thread isn't an easy task, so perhaps you
>> might want to consider whether it's possible to avoid doing so.
>> Maybe
>> you can draw into an image on the secondary thread, and then when
>> necessary use -performSelectorOnMainThread:withObject:waitUntilDone:
>> to send the view a -setNeedsDisplay: message when after your thread
>> has completed its drawing. Then the view can overwrite its own
>> buffer
>> with it. Be careful of synchronization issues, of course.
>>
>> HTH,
>> --Kyle Sluder
>
> _______________________________________________
>
> Cocoa-dev mailing list (<email_removed>)
>
> Please do not post admin requests or moderator comments to the list.
> Contact the moderators at cocoa-dev-admins(at)lists.apple.com
>
> Help/Unsubscribe/Update your Subscription:
> http://lists.apple.com/mailman/options/cocoa-dev/graham.<email_removed>
>
> This email sent to graham.<email_removed>
DATE : Fri May 02 17:30:51 2008
OK, I have managed to implement this after a lot of poring over the
docs. I'm not sure if it's the most efficient way to actually handle
the thread communication, but it does work (using NSMachPort). As I
hoped, there isn't a big problem with drawing the graphics as they
should be, apart from a bit of occasional blank output when the window
is resized - BUT, performance is terrible. Worse than synchronous
drawing by a long shot - I haven't measured it because it's really
obvious how much the drawing lags the user - must be an order of
magnitude slower.
Question is of course, why? The thread is calling the exact same
drawing code as the view normally does, so that's a constant. So it
must be down to the thread communication/overhead. Or perhaps just my
minimal understanding of threads, locks, ports etc. ;-)
When I need to draw, I flag that fact to the class (the class is
handling all views using a single secondary thread, but in fact I only
have tried it so far with one), and the class bundles the update rect
and view into an NSInvocation, which is placed in a queue. An
NSPortMessage is used to wake up the thread which pulls the invocation
off the queue and invokes it, which does the drawing. Maybe the use of
NSInvocation is slow? Not sure why it should be.
Here's the pertinent parts of the code - have I done something silly?
(all vars starting with 's' are static globals):
+ (void) setDrawUsingSecondaryThread:(BOOL) threaded
{
if ( threaded && !sThreadedDrawing )
{
// create the thread and a port for it
NSPort* port = [NSMachPort port];
[port setDelegate:self];
// create a queue for the drawing invocations that are received -
this is done to maintain order when
// several views may be needing update.
sDrawingThreadInvocationQueue = [[NSMutableArray alloc] init];
// create a lock used to mediate access to the queue
sQueueLock = [[NSLock alloc] init];
// start the thread
[NSThread
detachNewThreadSelector:@selector(secondaryThreadEntryPoint:)
toTarget:self withObject:port];
sThreadedDrawing = YES;
sDrawingThreadPort = port;
}
sDrawingThreadShouldRun = threaded;
}
+ (void) secondaryThreadEntryPoint:(NSPort*) port
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* loop = [NSRunLoop currentRunLoop];
[loop addPort:port forMode:NSDefaultRunLoopMode];
NSDate* endDate = [NSDate distantFuture];
do
{
[loop runMode:NSDefaultRunLoopMode beforeDate:endDate];
}
while([self updateThreadShouldRun]);
[loop removePort:port forMode:NSDefaultRunLoopMode];
[port invalidate];
[pool drain];
}
+ (void) signalSecondaryThreadShouldDrawInRect:(NSRect) rect withView:
(NSView*) aView
{
if([self drawUsingSecondaryThread])
{
NSInvocation* invocation = [NSInvocation
invocationWithMethodSignature:[aView
methodSignatureForSelector:@selector(drawContentInRect:)]];
[invocation setTarget:aView];
[invocation setSelector:@selector(drawContentInRect:)];
[invocation setArgument:&rect atIndex:2];
// queue the invocation for processing by the thread
[sQueueLock lock];
[sDrawingThreadInvocationQueue insertObject:invocation atIndex:0];
[sQueueLock unlock];
// tell the thread to process the queue:
NSPortMessage* pm = [[NSPortMessage alloc]
initWithSendPort:sDrawingThreadPort receivePort:nil components:nil];
[pm setMsgid:1234];
[pm sendBeforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[pm release];
}
}
+ (void) handlePortMessage:(NSPortMessage*) portMessage
{
// receive the update request on the secondary thread via the port
message
unsigned msg = [portMessage msgid];
if( msg == 1234 && [sQueueLock tryLock])
{
NSInvocation* inv = [[sDrawingThreadInvocationQueue lastObject]
retain];
if ( inv != nil )
{
[sDrawingThreadInvocationQueue removeObject:inv];
[sQueueLock unlock];
NSAutoreleasePool* pool = [NSAutoreleasePool new];
NSView* aView = [inv target];
if( aView != nil && [aView isKindOfClass:[NSView class]])
{
if([aView lockFocusIfCanDraw])
{
[inv invoke];
[[aView window] flushWindow];
[aView unlockFocus];
}
}
[pool drain];
[inv release];
}
else
[sQueueLock unlock];
}
}
On 2 May 2008, at 9:07 pm, Graham Cox wrote:
>
> On 2 May 2008, at 6:13 pm, Kyle Sluder wrote:
>> As for spawning multiple threads, you want your drawing to be
>> performed really quickly. I would strongly advocate keeping a thread
>> around for the life of your view and having it sit an a loop that
>> ends
>> with it performing a blocking read on some IPC port. That way it
>> gets
>> scheduled off the processor but you don't suffer the thread-creation
>> or -destruction penalty every time you perform a draw.
>
> From what you're saying, I should have a worker thread that waits
> for a request to draw, do the drawing, then go back to sleep until
> next time. What isn't clear is if this thread should be per-view, or
> used by all views that work this way, and how exactly it should be
> flagged to do the drawing (and all the sleep issues, etc). I'm not
> sure what an IPC port is (I will look it up) but I get the general
> idea. If you can point me in the right direction to answer some of
> these, I'm happy to experiment with what's needed on the drawing
> side of things (and I'm hoping it's relatively little different from
> the single-threaded case).
>
> So my question at this stage isn't about drawing but about setting
> up and controlling the worker thread, (I think).
>>
>> Drawing from a secondary thread isn't an easy task, so perhaps you
>> might want to consider whether it's possible to avoid doing so.
>> Maybe
>> you can draw into an image on the secondary thread, and then when
>> necessary use -performSelectorOnMainThread:withObject:waitUntilDone:
>> to send the view a -setNeedsDisplay: message when after your thread
>> has completed its drawing. Then the view can overwrite its own
>> buffer
>> with it. Be careful of synchronization issues, of course.
>>
>> HTH,
>> --Kyle Sluder
>
> _______________________________________________
>
> Cocoa-dev mailing list (<email_removed>)
>
> Please do not post admin requests or moderator comments to the list.
> Contact the moderators at cocoa-dev-admins(at)lists.apple.com
>
> Help/Unsubscribe/Update your Subscription:
> http://lists.apple.com/mailman/options/cocoa-dev/graham.<email_removed>
>
> This email sent to graham.<email_removed>
| Related mails | Author | Date |
|---|---|---|
| Graham Cox | May 2, 09:13 | |
| Kyle Sluder | May 2, 10:13 | |
| Graham Cox | May 2, 13:07 | |
| Jean-Daniel Dupas | May 2, 13:20 | |
| Jens Alfke | May 2, 17:27 | |
| Graham Cox | May 2, 17:30 | |
| Jean-Daniel Dupas | May 2, 18:04 | |
| Duncan | May 3, 05:40 | |
| Graham Cox | May 3, 06:51 | |
| Duncan | May 3, 13:36 | |
| Jean-Daniel Dupas | May 3, 13:57 | |
| Graham Cox | May 3, 14:52 | |
| Jean-Daniel Dupas | May 3, 15:22 | |
| Graham Cox | May 3, 15:30 | |
| Ricky Sharp | May 3, 15:44 | |
| Jean-Daniel Dupas | May 3, 15:55 | |
| Graham Cox | May 3, 16:11 |






Cocoa mail archive

