alternative to switch/control-flow for single use case?

If I have a custom class that conforms to something like AVCaptureVideoDataOutputSampleBufferDelegate (called thousands of times), and I know that I want to save only the first image frame and then do something with all the subsequent image frames, then something like this code is (I guess) fairly standard.


public class myFirstClass {

    var firstImageProcessed = false

    func saveFirstImage() {
        // do some stuff
    }
    func doWorkOnImageFrame() {
        // do some stuff
    }

    public func processImageFrame_delegateMethod() {

        switch self.firstImageProcessed {
        case true:
            self.doWorkOnImageFrame()
        default:
            self.saveFirstImage()
        }
    }
}


Is there a way to avoid the unnecessary 'switch', in the situation where I know for sure that I only want the first frame? The following obviously doesn't work, but is there a way with assigning functions to a variable that would avoid calling a switch statement 10,000 times just for that very first case?


public class mySecondClass {

    var methodToCall = saveFirstImage

    func saveFirstImage() {
        // do some stuff
        self.methodToCall = doWorkOnImageFrame
    }
    func doWorkOnImageFrame() {
        // do some stuff
    }

    public func processImageFrame_delegateMethod() {
   
        methodToCall
    }
}



cheers,

Answered by donarb in 134997022

Your code was close, this should work, although method calls have more overhead than switch statements so you're probably not optimizing much.


public class mySecondClass {

    var methodToCall = mySecondClass.saveFirstImage

    func saveFirstImage() {
       // do some stuff
       methodToCall = mySecondClass.doWorkOnImageFrame
   }
  
    func doWorkOnImageFrame() {
       // do some stuff
   }
  
    public func processImageFrame_delegateMethod() {
       methodToCall(self)()
   }
}

I must be missing sthing. Why can't you just do this :


if firstImageProcessed {
       self.doWorkOnImageFrame() 
} else {
       self.saveFirstImage() 
 }


There must be a place where firstImageProcessed is set to true of course.

I can do that, what I want to know is if I can avoid calling that if statement 10,000 times when I know it's only going to be false once (the first time).

I do not see enough code to be sure of the solution. Who is calling that if 10 000 times ?


May be, insert a return in the calling function


func callingFunc() {
     if firstImageProcessed { 
            self.doWorkOnImageFrame()  
            return
     } else { 
            self.saveFirstImage()  
     }
}
Accepted Answer

Your code was close, this should work, although method calls have more overhead than switch statements so you're probably not optimizing much.


public class mySecondClass {

    var methodToCall = mySecondClass.saveFirstImage

    func saveFirstImage() {
       // do some stuff
       methodToCall = mySecondClass.doWorkOnImageFrame
   }
  
    func doWorkOnImageFrame() {
       // do some stuff
   }
  
    public func processImageFrame_delegateMethod() {
       methodToCall(self)()
   }
}

thanks! I just tested it (roughly) and it doesn't make sense to me that the 'switch' version is quicker. I would have thought that given there's the same number of method calls in both tests, but one run has the addition of a switch control each iteration that that would be slower?


EDIT: just to be clear, the code in the methods really needs to stay there, and can't be incorporated into the switch statement. this is all really just about branching off once (and once only) to do something the very first time. otherwise, always call method2.


results (run on iPhone5):

var function swap duration: 0.118873000144958

switch method duration: 0.00195604562759399


override func viewDidAppear(animated: Bool) {
  
     let count = 100000
     var duration: Double
     var timeStarted: NSDate
  
     // benchmark var function swap method
     timeStarted = NSDate()
     for _ in 0...count { 
          methodToCall(self)()
     }
     duration = NSDate().timeIntervalSinceDate(timeStarted)
     print("var function swap duration: \(duration)")

     // benchmark switch control method
     var firstTime = true
     timeStarted = NSDate()
     for _ in 0...count {
          switch firstTime {
          case false:
               self.methodTwo()
          default:
               self.methodOne()
               firstTime = false
          }
     }
     duration = NSDate().timeIntervalSinceDate(timeStarted)
     print("switch method duration: \(duration)")
}
  
func methodOne()->Void {
     self.methodToCall = ViewController.methodTwo
}
func methodTwo()->Void {
}

I'm processing image frames from the camera, which are delivered to a function of AVCaptureVideoDataOutputSampleBufferDelegate. This will deliver potentially thousands of frames, and my thinking is that having to do an if { } else { } test every time is wasteful/inefficient, if I know that it's only the very first frame that I need that test for.


cheers

Classic case of premature optimization IMHO.


Don't worry about it unless it's actually a problem.

well, not really. I'm not worried about it, just interested in how the language works.

Well, your switch case is only two values, which I assume that the compiler can optimize very well. And your method calls take no parameters (except for the hidden self) and return nothing, so the stack frame setup/teardown overhead are neglible. Now, if your switch statement had multiples tests and branches, the method call alternative may be faster.

One of the reason people (including me, but junkpile got there first) say "premature optimization" like it's a bad thing is that, without a goal of what unsatifactory metric you want to improve, you don't know what it means to change your code.


1. You apparently tested this code in a UIViewController subclass, which is an Obj-C class and therefore dynamically dispatched. That means there's an overhead in objc_msgSend whenever a method is called, as well as a system method cache and possibly other overheads.


2. Because you're doing this in Swift, you don't know what that changes about the method invocation mechanism. Your methodToCall variable is of closure type, and it's not obvious how that integrates with the Obj-C runtime. (It may be translated into an Obj-C block, which may need to be copied onto the heap. It may be translated into a selector. It may even in some cases be translated into a direct call, or inlined at the call site. )


3. You didn't try to control for whether it's slower to invoke any method the first time, if that causes a change in the method cache or other housekeeping activities.


4. You don't know whether the indirect method invocation has thread synchronization implications (such as incrementing a reference count, which involves a lock).


5. You didn't say whether you did this in a debug (unoptimized) or released (optimized) build.


6. Aside from all that, you don't know how the tests interact with the hardware.


Perhaps — taking this to an impossible extreme — both cases are handled by a single machine instruction, an indirect jump in one case and test-and-branch in the other. It's possible that the first is much slower at the hardware level at the second. It's possible that your toy code is so small that it sits in L1, L2 or L3 cache in a way that real code won't. It's possible that the test-and-branch code works better with the CPU's branch prediction logic than the indirect jump code.


In the light of all that (and other things I probably haven't thought of), what did you really learn about how the language works? I certainly don't know.


This is not to say you shouldn't think about these issues. It's more of a general warning against relying on micro-benchmarks to resolve a larger performance question. In almost every case, the outcome of using a micro-benchmark as a guide is to optimize the micro-benchmark, but do nothing useful for the real code, except perhaps to make it more complicated to understand.


That's why we say "premature optimization" like it's a bad thing.


[Apologies for the rant.]

But I'm not really trying to optimize any code (that benchmark could hardly be likened to an attempt to optimize). I had the code wrong for swapping functions using variables, which was helpfully corrected; something I have now learned. It took all of five minutes to wrap a start-stop timer around two pairs of loops. I'm surprised by the result, but I'm not about to spend ten years researching why. I'm not about to hook up an oscilloscope to the iPhone and check voltage signalling.


I think you're making a few assumptions. There's no context to imply I'm trying to optimize by cherry picking bits of code, or that this is even anything more than 'how does this work'. I'm talking/asking about how something works in Swift, not best practice for optimizing/developing code.


Your points 3 and 4 are interesting. But I think that given that both runs invoke exactly the same methods in the same order (one first, and the second lots of times), it's probably a lot to do with the invoking of a function via a variable (the mechanism of that) rather than simply invoking a function. Which, for me, is interesting, and while it won't change any code I write it's one of those things that's worth remembering.


Writing code with a single thought about its efficiency is just as bad as micro-benchmarking every line you produce.

FarOeuf >> There's no context to imply I'm trying to optimize by cherry picking bits of code …


FarOeuf >> Is there a way to avoid the unnecessary 'switch' … ?


Hmm. Avoiding "unnecessary" code does sound a lot like optimization to me. But I'll take your word for it.


>> Writing code with a single thought about its efficiency is just as bad as …


Sure, I but I'd like to sell you the idea that thinking about it at a higher level is more likely to provide realistic benefits. After all, decoding 100,000 frames is likely to be a multi-second operation (at least). Improving this somewhere between 0.002 and 0.1 seconds probably isn't going to be worth your development time. Looking for a way to avoid decoding (say) 20% of the frames might be a better use of your time, if your use scenario permitted.


Finally, when I went back and read your original post, I realized there is an easy optimization you can do that seems guaranteed to improve performance, even if the improvement is too small to matter. You could set a different delegate during the first callback.The overhead of calling the delegate method on each frame is basically the same, no matter which object is the delegate.


I mention this because, in a scenario where it really affected performance to call one method 100,000 times, the best solution would be at the calling site: call method 1 one time, and call method 2 99,999 times. You can't change what's happening at the calling site in this case, but you can change the delegate.

alternative to switch/control-flow for single use case?
 
 
Q