input performance with multiple threads

Nibbie
Posts: 4
Joined: 2008.11
Post: #1
I've moved my rendering into a separate thread to get control over my run loop, however, I'm seeing some ridiculous performance issues when dragging my finger on the screen.

Has anyone dealt with this? Have any solutions?

Here's the simplest example I could come up with that exhibits the problem.

Code:
- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // Override point for customization after application launch
    [window makeKeyAndVisible];
    
    lastTime = CFAbsoluteTimeGetCurrent();
    [self performSelectorInBackground:@selector(updateLoop) withObject:nil];
}


- (void) updateFPS {

    NSTimeInterval time = CFAbsoluteTimeGetCurrent();
    double dt = time - lastTime;
    lastTime = time;

    fps.text = [NSString stringWithFormat:@"%d",(int)(dt*1000)];
}


- (void) updateLoop {
    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    BOOL Done = NO;
    
    while (!Done) {
        
        NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
    
        [self performSelectorOnMainThread:@selector(updateFPS) withObject:nil waitUntilDone:YES];

        [loopPool release];
    }
    
    [pool drain];
}

On my first gen iPhone, this loop runs at 5ms, but if I start dragging my finger around on the screen it drops to 11ms, sometimes spiking to 20ms or more.

Playing with thread priorities doesn't make this go away.

Thoughts?
Quote this message in a reply
DoG
Moderator
Posts: 869
Joined: 2003.01
Post: #2
All your update loop does is call something on the main thread, which clearly needs to be synchronized, and can't be performed until the main thread is done being busy. And when there's input to be processed, and the main thread does that, well, you're seeing the expected results. Worst case, -updateFPS gets called less frequently than if it was run by a timer on the main thread.

Moving your rendering to a separate thread only makes sense if your critical sections are short, and you don't do such a silly thing as in the above example.
Quote this message in a reply
Nibbie
Posts: 4
Joined: 2008.11
Post: #3
Well, I simplified it here to absurdity to make a point. Processing input, particularly when the app doesn't even have any defined handlers, should not take 5ms! Is there a way to poll instead? Or disable input handling on the main loop, so it can be handled more smartly/efficiently in a custom runLoop?

What's killing me is that in my actual app, I've got two threads. A render thread pinged at 60hz (limited only by the gpu refresh rate), and an update thread which runs much slower (about 20hz currently) with the render thread interpolating states.

What I'm seeing is that the slower my update thread runs, the worse the per-frame input handling becomes.

For instance, if I artificially inflate the above example with the following code:
Quote: int total = 0;
const int limit = 5000;
for (int i = 0; i < limit; i++)
for (int j = 0; j < limit; j++)
total *= total;

... my update takes about 200ms, and touching the screen drops it to 250ms!

That means in a sampling of 200ms of my secondary thread, it has been interrupted enough times to have sucked up 1/4 of the processing time (or 20% of the total time) to do NOTHING!?! Just because I'm dragging my finger around the screen. That makes no sense. Do we have any control over this? I did a bunch of searching, and found some examples where people were able to enumerate the ports for the main run loop and remove them, but those interface don't appear to be exposed in the iPhone SDK.
Quote this message in a reply
Moderator
Posts: 3,572
Joined: 2003.06
Post: #4
Well, did you stop calling performSelectorOnMainThread, or are you still doing that?

There is never a need to poll for input. Just wait for it as usual in your main thread and flag some variables so your update gets it when it needs it.
Quote this message in a reply
Moderator
Posts: 3,572
Joined: 2003.06
Post: #5
Just for the sake of sharing what I've seen so far: The way it's done in GLSprite is very roughly how I do things. The timer path works best for me. I've tried doing a separate thread for rendering/updating but unlike a few others around here, my results show roughly the same performance (actually worse by a frame or two per second, using threaded rendering, if anything). My testing was done using an app which I've nearly completed and uses plenty of lit and depth-tested 3D geometry, mixed with plenty of blended sprites.

So not only did threaded rendering do nothing for me, but maybe cost me a hair of performance. Further, it complicated matters if I wanted to use other UIKit views for certain things. I just haven't been able to find any practical advantage to threaded rendering on iPhone vs using the standard timer path ... yet. My sense is that, for best flexibility and convenience, the threaded approach on iPhone probably isn't worth the extra hassle, just for a few frames (if you can find 'em). There are other ways to improve rendering performance for greater gains and less hassle, IMHO.

Also, I've read in a few places where some folks were having terrible problems with input processing, screwing up timing. I have had very few problems with input so I don't know where those issues come from.
Quote this message in a reply
DoG
Moderator
Posts: 869
Joined: 2003.01
Post: #6
If you insist on calling performSelectorOnMainThread:, you're not gonna see improved performance. And, an observed 25% time used for input processing isn't a lot, if you consider the machinery things have to go through.
Quote this message in a reply
Nibbie
Posts: 4
Joined: 2008.11
Post: #7
Clearly my simplified example has failed to communicate the problem. Can maybe someone provide an example using a secondary thread at 100% usage (e.g. not sleeping or blocked for a significant period of its cycle) that does not exhibit a 20% performance drop when dragging your finger on the screen. (Switching the performSelectorOnMainThread to just dumping the frame time to the console via NSLog doesn't change anything - though I'm not familiar with the synchronization issues that NSLog may have. In my actual app, I'm displaying the framerate using OpenGL without crossing any thread barriers fwiw.)

In any case, I cannot possibly see how that's acceptable. Maybe someone can explain the machinery its going through that could possibly explain such a hit. My understanding was the the iphone has a dedicated chip for handling the touchscreen. My assumption from there was that the main thread then maybe gets interrupts for move events, which get pushed onto the runLoop's event queue and handled on the next iteration.

In a single-threaded app using a timer, if your update takes too long, it sounds like it's possible to starve input handling since user events have higher priority than input events.

Moving your update loop to a separate thread should then keep the main thread idle most of the time, and only wake up when a new event (such as dragging your finger on the screen) needs to be handled.

In my actual app, I'm not using performSelectorOnMainThread. Instead, I have a single NSLock which I use when handling input on the main thread.

Code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event touchesForView:self] anyObject];

    [myLock lock];
    location = [touch locationInView:self];    
    [myLock unlock];
}

Then in my update loop on a separate thread, I use tryLock to grab the latest value (but avoid blocking if the main thread is in the middle of updating it).

Code:
- (void) updateInput {

    if (![myLock tryLock])
        return;

    CGPoint newLocation = location;
    [myLock unlock];

    ... do stuff using newLocation ...
}

But even commenting all of that out, and removing any handling of input and use of locks, I'm still seeing this hit when dragging my finger on the screen.
Quote this message in a reply
Moderator
Posts: 3,572
Joined: 2003.06
Post: #8
aleiby Wrote:In any case, I cannot possibly see how that's acceptable.
Losing some measure of performance because of input processing and timing does not seem terribly unacceptable and/or unexpected to me (especially in the higher frequency ranges). It is an iPhone after all, not a desktop... But admittedly, I'm not all that picky about everything. Wink

aleiby Wrote:In a single-threaded app using a timer, if your update takes too long, it sounds like it's possible to starve input handling since user events have higher priority than input events.

I guess, theoretically, but in practice, even if I'm doing a complex scene and bogging down to 20 fps or lower, I'm still getting 60 ticks of simulation and not missing any input (except for maybe a missing touch up on occasion, which I suspect may be my fault in one of my logic routines). I don't have a clue why I've heard others missing input for maybe up to seconds. Maybe they may have set their render timers to an unacceptably high value? I set mine to 60 and do just fine like that.
Quote this message in a reply
Member
Posts: 32
Joined: 2008.10
Post: #9
aleiby Wrote:I've moved my rendering into a separate thread to get control over my run loop, however, I'm seeing some ridiculous performance issues when dragging my finger on the screen.

Has anyone dealt with this? Have any solutions?


Code:
- (void)applicationDidFinishLaunching:(UIApplication *)application {    
- (void) updateLoop {
    
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    BOOL Done = NO;
    
    while (!Done) {
        
        NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
    
        [self performSelectorOnMainThread:@selector(updateFPS) withObject:nil waitUntilDone:YES];

        [loopPool release];
    }
    
    [pool drain];
}

You are running a tight loop - while (1) effectively. The best solution is to introduce a lock inside the while loop, for 2 reasons. One, it provides synchronisation with events outside your thread, and two, it can force a reschedule.

Eg.
Code:
while (!done)
{
   aLock->Lock();
   Render();
   aLock->Unlock();
}

You also need the lock to prevent touch screen input code from running in the middle of a render cycle (causes all sorts of sideeffects)
eg.
Code:
- (void)touchesBegin
{
   aLock->Lock();
   do something
   aLock->Unlock();
}

I am using a render thread and my frame rate went up 20%, and I do not lose any input.
Quote this message in a reply
Nibbie
Posts: 4
Joined: 2008.11
Post: #10
I've tracked this down to the 'loginwindow' process.

Here's what I did:
  • Launch Instruments
  • Select CPU Sampler
  • Click on the CPU Monitor track to select it
  • Click the "% CPU" column twice so the arrow points down to sort by descending order
  • Select any of your own apps from the Launch Executable drop down
  • Click record, let it run for 5-10 seconds and click Stop (same button)
  • Repeat the previous step, but this time make sure you continuously rub the iPhone screen with your finger (to generate move events)
Now, you can compare the two runs by clicking the arrows in the timer pane at the top. What I'm seeing is that when I rub the screen, the loginwindow process is sucking up about 20% CPU (and is idle otherwise).

I don't have any other devices to test this on, so maybe you guys can see if you get similar results and post which device you tried it on.

Anyone know what this process does? There's some mention of it here, but that doesn't really explain what's going on with the iPhone.
Quote this message in a reply
Member
Posts: 93
Joined: 2008.11
Post: #11
I'm having the same issue on 1st generation iPhone with 2.0.2 firmware.
I don't even have any input controlling routines but still when I touch/drag the screen I have a major performance downgrade..
Quote this message in a reply
Nibbie
Posts: 2
Joined: 2008.12
Post: #12
I have exactly the same problem with ipod-touch 1st gen whether I use a separate render thread or a main thread timer (I also noticed it's present in 2nd gen ipod-touch but hardly noticeable since the machine is 20% faster than 1st gen).

I contacted apple technical support about this, and they could not give me a solution, other than to put a request in for a feature to control how the OS polls the hardware (similar to how you can control the polling rate of the accelerometer).

So for now, it looks like we are stuck with the problem?

-Steve
Quote this message in a reply
Member
Posts: 93
Joined: 2008.11
Post: #13
try #define kAccelerometerFrequency 0.0
It used to help me a lot. Not with performance of random finger sliding and touching but with the accelerometer which makes everything very laggy if you don't really use it and the kAccelerometerFrequency is not set to 0.0. I used to develop my application on top of CrashLanding and I didn't notice the kAccelerometerFrequency constant so it was set to 100 hz and I had a lot of lags in my app.

HTH,
Alex
Quote this message in a reply
Nibbie
Posts: 2
Joined: 2008.12
Post: #14
thanks for the info godexsoft. I've already disabled the accelerometer by setting a huge udpate interval.

If you look at the crash landing code, setting kAccelerometerFrequency to 0.0 is not quite right since will cause a divide by zero. It might just be luck that the resulting undefined float is stopping the accelerometer:

Crash lander code:

//Configure and start accelerometer
[[UIAccelerometer sharedAccelerometer] setUpdateIntervalSad1.0 / kAccelerometerFrequency)];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];

I use the following method which makes the accelerometer update once every 1000 seconds:

// Turn off accelerometer
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1000.0f];

Off course, this is all a moot point for the actual problem talked about here. Touching the screen on 1st-gen ipod really hits performance. I've found the easier way to show the problem is to simply touch and hold five fingers on the screen (you don't actually have to move your fingers around on the screen).

-Steve
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Input performance with multi touch godexsoft 6 3,483 Jan 17, 2009 12:04 AM
Last Post: longjumper