Function performance

Suppose I have an array of strings, and I want to find the index of a given string in that array. I'm wondering whether there will be much of a performance difference between these two functions:


func indexOfName(name: String) -> Int? {
  for (index, element) in names.enumerate() {
    if element == name {
      return index
    }
  }
  return nil     
}


func indexOfName(name: String) -> Int? {
  return names.enumerate().filter { $0.element == name }.first?.index
}


Or is the difference between them simply one of notation?

Answered by QuinceyMorris in 172875022

There'll be some performance difference, in general, because the second one creates an array to hold the results, which means it likely has to allocate memory.


However, you should be using the standard 'indexOf' method (Swift 2) or 'index (of:)' (Swift 3). That way you can take advantage of any optimizations built into the standard library.

Accepted Answer

There'll be some performance difference, in general, because the second one creates an array to hold the results, which means it likely has to allocate memory.


However, you should be using the standard 'indexOf' method (Swift 2) or 'index (of:)' (Swift 3). That way you can take advantage of any optimizations built into the standard library.

In such a situation, best is to test explicitely the perf, by iterating (10 000 or 100 000 times) and compare.


var names = []
for i in 1...100 {
     names.append("aName"+String(i))
}
let start = NSDate()
for _ in 0..<100000 {
     let index = indexOfName(name: "aName55")
}
end = NSDate()
Swift.print(end.timeIntervalSinceDate(start))


And do the same for any other implementation.

I'd strongly argue that this is not a useful approach. It's called "microbenchmarking" and it really tells you nothing except the performance of the microbenchmark itself. There is no answer there for a real-world situation.


There are lots of reasons for this, including trivial ones like the compiler perhaps being able to optimize loops with slight differences in different ways.


But I think the important reason in this case is that in Albinus's examples, the one that allocates memory is much more expensive than the other one for small array sizes. For large array sizes, the single array allocation isn't significant.


Here are two good rules to follow when considering performance:


1. Don't apply optimizations until you see a performance problem.


Note that the problem could be excessive CPU use, but it could also be excessive memory use, or even excessive battery drain. (On a mobile device, you might really want your code to run a little slower, if that produces a big reduction in battery use.)


2. Measure the performance of the problem code, and measure the performance of proposed solutions.


Keep the solution that solves the problem. There's absolutely no point in measuring simplified code (microbenchmarking). It's also a mistake to assume that a "more efficient" algorithm will solve the actual problem faster. You have to measure to find out.

Hi Quincey,

agree with point 1, absolutely.


But for point 2, once I know there is a performance issue, if it's easy to build the test case in the real code, OK ; but there may be situations where I want to check the different options, without building the test case in the real code, then I do not see what is so bad in microbenchmarking.


BTW, for curiosity, in this case, on microbenchmark (sorry !!!!) shows your reasonning was quite correct (even if the test in playGround may not be representative of real case conditions):

solution 2 is the worst, 1 is nearly 40 times faster and indexOf is stil 30% faster than 1 ; will that make a difference in Albinus app performance ?


So I would add a rule 3 : it's always better to understand what's going on behind the hood, but it's another story.

>> once I know there is a performance issue … I want to check the different options, without building the test case in the real code


One problem with this is it assumes you know what the cause of the performance issue is. Generally, you don't, and your chances of guessing correctly are pretty low.


Another problem is that investigating performance of different code assumes that it has the same performance characteristics as the original code. Generally, that's not true.


For example, if you're trying to improve the performance of a loop, and the loop calls a method every iteration — surely not an uncommon scenario — it could easily happen that in a smaller test case the compiler determines that the method can be compiled as 'final' (not dynamic) and perhaps even can inline the method body at the call site. In your real code, it might be forced to use a dynamic call instead. Your test case results might lead you to the wrong solution.


However, you're correct in that developing alternate solutions might be easier in a test bed than in the real code. With gross performance problems (e.g. too many memory allocations), this might be enough.

Function performance
 
 
Q