El Capitan - very slow dispatch_sync result for main_queue

I am teaching myself a little about concurrency using GCD. I made good progress and was able to greatly improve app responsiveness. I noted when running apps on El Capitan there was a tremendous slow down. I experimented and developed a very simple sample code that shows a 400 times difference in execution speed between OS 10.11 and OS 10.10.


The test code is very simple - creates a dispatch queue that submits three jobs:

1) Record the start time

2) Run a simple loop and update an NSSlider

3) Print the total time of execution.


When updating the interface, I use "dispatch_sync(dispatch_get_main_queue() ... " to ensure my interface calls are executed on the main queue. I use sync to ensure that update of the interface element is smooth.


Here is the weird thing, when executing the sample code below El Capitan is PAINFULLY slow.


10.11 Execution Time: 16.6 seconds

10.10 Execution Time: 0.04 seconds


Nearly 17 seconds to loop though only 1000 times!!!!


If I use "dispatch_async" for the main queue calls (line 21) both 10.11 and 10.10 execute the code in 0.002 seconds - however, for longer loops the display is not smooth - I need to use sync to get the desired effect.


Is this a known issue with El Capitan? I have not seen any reference to this, but have notice that people are having issues with iOS 9 that could be related to this.


Thanks.


class ViewController: NSViewController {
    @IBOutlet weak var slider: NSSlider!
   
    let limit = 1000
    var startTime: NSDate!
   
    let serialQueue = dispatch_queue_create("serialQueue", nil)
    override func viewDidLoad() {
        super.viewDidLoad()
        self.slider.integerValue = 0
        self.slider.maxValue = Double(limit)
    }

    @IBAction func start(sender: AnyObject) {
        dispatch_async(serialQueue, { self.startTime = NSDate() } )
        dispatch_async(serialQueue, { self.rampUp() } )
        dispatch_async(serialQueue, { print(self.startTime.timeIntervalSinceNow) } )
    }
   
    func rampUp() {
        for i in 0...self.limit {
            dispatch_sync(dispatch_get_main_queue(), {
                self.slider.integerValue = i
            })
        }
    }
}

Would you mind trying your last func like this?

func rampUp() {
  for i in 0...self.limit {
  CFRunLoopPerformBlock(NSRunLoop.mainRunLoop().getCFRunLoop(), kCFRunLoopCommonModes) {
  self.slider.integerValue = i
  }
  }
}

that is, going over the main run loop instead of the main queue. (I know they end up in the same place, but still...)

Thanks for the response. I gave CFRunLoopPerformBlock a shot and it has the same result as using dispatch_async in my previous function. The code runs very fast, but the slider does not move smoothly, it jumps once or twice during execution. To test I set the limit to 1,000,000. The program completes in under a second, but the movement of the slider is not smooth at all (jumps once and then only jumps to the end .. about a second after the loop has ended).


Tested in both 10.11 and 10.10 -- same result.


Using dispatch_sync in 10.10 and million loops takes about 50 seconds to run - the slider visibly moving while the program is running (no big jumping).

Using dispatch_sync in 10.11 and a million loops will take over 4 hours!!


After doing a little research I found this in the documentation for CFRunLoopPerformBlock: "This method enqueues the block only and does not automatically wake up the specified run loop. Therefore, execution of the block occurs the next time the run loop wakes up to handle another input source. If you want the work performed right away, you must explicitly wake up that thread using the CFRunLoopWakeUp function."


So I updated the funtion to this:

    func rampUp() {
        for i in 0...self.limit {
            CFRunLoopPerformBlock(NSRunLoop.mainRunLoop().getCFRunLoop(), kCFRunLoopCommonModes) {
                self.slider.integerValue = i
            }
            CFRunLoopWakeUp(NSRunLoop.mainRunLoop().getCFRunLoop())
        }
    }


While not perfectly smooth (like using dispatch_sync in 10.10), the NSSlider does "notch up" more consistently during the loop and only takes a second to run. Similar results in both 10.10 and 10.11.


So while this solution almost works, I still feel there is something not quite right about dispatch_sync(dispatch_get_main_queue() in El Capitan.


If running a data modeling appication and speed is important (not the display of the model's progress) then CFRunLoopPeformBlock with the following wakeup call works great - infact - a good performance boost over using the dispatch_sync (the working version in 10.10). However, if the interface needs to smoothly and accurately represent the data model in real-time, then dispatch_sync works for 10.10. The CF solution nearly works in El Capitan, but not as nicely (from an aesthetics point of view) as dispatch_sync in 10.10.

"If you want the work performed right away, you must explicitly wake up that thread using the CFRunLoopWakeUp function."

Thanks for that tip, I'll try to incorporate that into my code, too.


I think this points out that the main loop has to take care of both external events and queued blocks in a timely fashion. As was pointed out to me by Kyle Sluder previously, dispatch_sync(dispatch_get_main_queue()) {...} means that queued blocks will get in front of external events in the main loop; using the CFRunLoop queues by arrival. (May no longer be valid for El Capitan?)


No idea if this explains anything about your original results.

Thanks - I really appreciate your input.


The more I think about this, the more I suspect it might be the actual update of the interface itself rather than the dispach_sync command. Most interface updates should be done with dispatch_async so you are not waiting, unless you have the situation where you need to interface to match the model (which was my simple test case).


Regardless, I have learned a lot more about concurency. Still, I would enjoy getting an explination by an Apple Engineer on why that very simple sample code runs 400 times slower in El Capitan!

El Capitan - very slow dispatch_sync result for main_queue
 
 
Q