1

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?

Daniel S.
  • 6,458
  • 4
  • 35
  • 78
  • Why can't you use the Time Profiler? It should show you where the most time is spent. You even can record waiting threads to see if the main thread is blocked somewhere. Other question: do you use custom run loop modes in your code? – yurish Dec 20 '13 at 16:39
  • run loop modes: No, I don't use any custom run loop modes. – Daniel S. Dec 20 '13 at 16:42
  • Profiler: I tried the profiler once, but I couldn't find the information I wanted. I also think that it should be possible (well, that's what a profiler is for), but it was too complicated for me. If you have a link to a good tutorial, let me know. – Daniel S. Dec 20 '13 at 16:42
  • Because everything seems to have good performance, it must be some concurrency/blocking/waiting problem. Recording where threads wait for how long should solve the problem quickly. I just don't know how to do that exactly. – Daniel S. Dec 20 '13 at 16:45
  • Allright, desciptions for recording thread wait time don't sound too complicated. I will try that in the meantime. – Daniel S. Dec 20 '13 at 16:53
  • It's not that hard. Launch "Profile" in Xcode, choose the Time Profiler template. Instruments will launch you program and start recording. Click on the "Stop" button. Click on the "i" button next to the Time Profiler area in the upper-left corner. In the settings dialog select "Record Waiting Threads". Close the dialog and click the "Record" button. This should start your program again. After some time stop the recording and in the "Call tree" pane at the button see where the time is spent. – yurish Dec 20 '13 at 16:53

0 Answers0