What causes CADisplayLink to fire only sporadically/get delayed?

621 views Asked by At

Maybe you don't need all this information to help me with this problem. The core questions is: What can cause a CADisplayLink to delay firing, and how to check for possible reasons?

I'm using a CADisplayLink timer to velocity-/inertia-scroll my own implementation of an audio waveform view:

- (void) startVelocityScrollTimer {
    dispatch_sync(dispatch_get_main_queue(), ^{
        if (_velocityScrollTimer == nil) {
            _velocityScrollTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(velocityScroll)];
            _velocityScrollTimer.frameInterval = 1;
            [_velocityScrollTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        }
    });
}

I'm experiencing problems with the accuracy of the timer. It usually fires 50 or 60 times a second (not sure, but exact number doesn't matter here). During certain actions, this rate goes down to about 3 to 5 fires per second, which is way too low for smooth scrolling. I am unable to identify what causes these delays.

I've tried:

  • Completely uncoupling drawing and loading by outsourcing the loading of audio data to a dedicated dispatch queue, which loads the data asynchronously.
  • Profiling and optimizing the drawing method. At peaks, it takes a maximum of 6ms.
  • Profiling the method called when the timer fires (velocityScroll:). It usually takes less than 1ms to execute, sometimes peaks are at 3ms.
  • using NSRunLoopCommonModes instead of NSDefaultRunLoopMode for the CADisplayLink timer

In the caching, I have a lot of dispatch_sync's and a few dispatch_barrier_sync's. Although most of them should be irrelevant, I've

  • profiled every dispatch-to-start time in my caching
  • profiled every execution time of dispatched blocks in the caching

Although most of them must be irrelevant. I didn't find any waits/blocking which took more than 50ms. This is a bit slow, but still I think it should not get the timer down to 5 fires/second or worse.

For profiling, I've not used a profiling tool (couldn't handle them). Instead, I used the typical approach:

double start = CACurrentMediaTime();

// code to profile here

NSLog(@"it took %.2fms", (CACurrentMediaTime() - start) * 1000);

Here is the method executed when the timer fires. The three methods in the middle are mostly plain simple position-calculations which are executed in almost no time, except the last one, which does a dispatch_sync to the main queue. However, this completes extremely quickly (less then 3ms, see above).

- (void) velocityScroll {
    Float32 cyclesPerSecond = 1.0 / _velocityScrollTimer.duration;

    [self applyScrollVelocityToScrollPosition:cyclesPerSecond];
    [self applyScrollVelocityDecelerationToScrollVelocity:cyclesPerSecond];
    [self ensureVelocityScrollOnlyActiveIfNeeded];

    [self setNeedsDisplay];
}

I know you guys would like to see more code, but there is a lot of code and I don't want to post all of it. Please ask for individual parts, which I will then post here, if you need them.

What could cause the delays between fires of the CADisplayLink timer or how to find it out?

0

There are 0 answers