yunhao
yunhao

Reputation: 94

Performance issue when using for-in loops with ranges

Swift provide a for-in style for-loops to substitute C-style for-loops. We can also use this kind of for-in loops with Range and ClosedRange.

for i in 0..<100 {
  // do something.
}

However, there are some performance issues when using for-in loops with ranges, especially when there are nested for-loops.

I use XCTest measure to test three kinds of loops:

final class LeetcodeSolutionsTests: XCTestCase {
    
    var count: Int = 1000
    
    func testForInClosedRange() {
        // Time: 1.016 sec
        measure {
            for i in 1...count {
                for j in 1...count {
                    let _ = i + j
                }
            }
        }
    }
    
    func testForInRange() {
        // Time: 0.542 sec
        measure {
            for i in 1...count {
                for j in 1..<(count + 1) {
                    let _ = i + j
                }
            }
        }
    }
    
    func testWhile() {
        // Time: 0.015 sec
        measure {
            for i in 1...count {
                var j = 1
                while j < count + 1 {
                    let _ = i + j
                    j += 1
                }
            }
        }
    }
}

From the example, the for-in loop with Range is 2 times faster than the for-in loop with ClosedRange. And the while loop is much faster than both of them.

I guess for-in loops will do some extra works, and it makes sense that there would be some subtle performance differences. What I don't expect is that the performance differences are so huge.

Upvotes: 0

Views: 258

Answers (1)

Andreas Oetjen
Andreas Oetjen

Reputation: 10199

Your problem appears only in a Debug configuration, because the compiler generates a huge amount of access checking code.

If you

  • modify your code in a way that it uses the variables (to prevent the compiler to just throw away all the unused code)
  • and then run it in release mode

you get the very same results for each kind of loop:

func measure(txt:String = #function, c:()->()) {
    let start=Date()
    c()
    let end = Date()
    let duration = end.timeIntervalSinceReferenceDate-start.timeIntervalSinceReferenceDate
    print ("\(txt) - Duration: \(duration)")
}

final class LeetcodeSolutionsTests {
    
    var count: Int = 1000
    
    func testForInClosedRange() {
        // Time: 0.001672 sec
        var result:Int = 0
        measure {
            for i in 1...count {
                for j in 1...count {
                    result += i + j
                }
            }
        }
        print (result)
    }

    func testForInRange() {
        // Time: 0.001671
        var result:Int = 0
        measure {
            for i in 1...count {
                for j in 1..<(count + 1) {
                    result += i + j
                }
            }
        }
        print (result)
    }
    
    func testWhile() {
        // Time: 0.001670 sec
        var result:Int = 0
        measure {
            for i in 1...count {
                var j = 1
                while j < count + 1 {
                    result += i + j
                    j += 1
                }
            }
        }
        print (result)
    }
}

let t = LeetcodeSolutionsTests()
t.testForInClosedRange()
t.testForInRange()
t.testWhile()

Which then gives you:

testForInClosedRange() - Duration: 0.0016720294952392578
1001000000
testForInRange() - Duration: 0.0016709566116333008
1001000000
testWhile() - Duration: 0.0016709566116333008
1001000000

Upvotes: 2

Related Questions