0

Why is for-in slower than while in swift debugging mode? I wrote this. Thanks for people who answer to me, I could have learned Seqeunce and IteratorProtocol.

So I implemented custom type ( School below code ) which conformed Sequence. And I checked Xcode-time profile.

But I can't find anything protocol witness enter image description here

But If only use range and for-in , time profiler show protocol witness.

enter image description here

why is indexingIterator.next() using dynamic method but not in School ? I thought that even struct conformed protocol, if variable in struct type use method of protocol, this method will be static method. If I am wrong, Could you please tell me what is wrong?

⬇️School code

struct SchoolIterator: IteratorProtocol {
    
    private var schoolList: School
    var idx = 0
    init(_ school: School) {
        self.schoolList = school
    }
    
    mutating  func next() -> String? {
        defer { idx += 1 }
        guard schoolList.count-1 >= idx
            else { return nil }
        
        return schoolList[idx]
    }
}

struct School: Sequence {
    fileprivate var list = Array(repeating: "school", count: 100000)
    var count: Int { return list.count }
    
    subscript(_ idx: Int ) -> String? {
        guard idx <= count-1
            else { return nil }
        return list[idx]
    }
    func makeIterator() -> SchoolIterator {
        return SchoolIterator(self)
    }
}
var schools = School()
for school in schools {
    print(school)
}


HyunSu
  • 155
  • 7
  • Oops, you found a mistake in my wording. `IndexingIterator.next` is not dynamically dispatched - the methods that `IndexingIterator.next` calls, such as `Collection.formIndex`, _are_. That's why you don't see a protocol witness for `next`, only ones for `formIndex`, `subscript` etc. – Sweeper Apr 10 '21 at 13:23
  • @Sweeper thank you so much! I now could understand what is problem. Even today is weekend, you answered to me. I really appreciate with you. – HyunSu Apr 10 '21 at 14:39

1 Answers1

2

Your for loop translates to:

var schools = School()
var iterator = schools.makeIterator()
while let school = iterator.next() {
    print(school)
}

Notice how nothing here is a protocol. schools is of type School, iterator is of type SchoolIterator, everything that next does (like accessing schoolList.count, or the subscript of schoolList) deals with structs too. The key point is that the compiler can figure out exactly which member you mean, because its (compile-time) type is a struct. There is no need to look up witness tables.

Compare that to, e.g.

func f<S: Sequence>(_ s: S) {
    for thing in s {
        ...
    }
/*
    var iterator: S.Iterator = s.makeIterator()
    while let thing = iterator.next() {
        ...
    }
*/
}

f(School())
f(1..<100)

How would the compiler dispatch the calls to iterator.next()? I've deliberately added the type annotation to make it clear what's happening - this time, the compiler doesn't know which next you mean. Is it IndexingIterator.next()? Or SchoolIterator.next()? Or SomeOtherIterator.next()? Keep in mind that I can call f with any kind of Sequence! That's why it needs to look up the witness table of the actual type of S.Iterator at runtime - it is impossible to figure out which next to call.

As for why for i in 0..<100 uses dynamic dispatch, well, on first sight, there seems to be all structs:

let range: Range<Int> = 0..<100
var iterator: IndexingIterator<Range<Int>> = range.makeIterator()
while let i = iterator.next() {
    ...
}

However, iterator.next actually does something like this:

public mutating func next() -> Elements.Element? { if _position == _elements.endIndex { return nil } let element = _elements[_position] _elements.formIndex(after: &_position) return element }

_elements is defined like this:

public struct IndexingIterator<Elements: Collection> {
  
  internal let _elements: Elements

_elements could be any kind of Collection, so again, we don't know which member _elements[_position] or _elements.formIndex refers to at compile time. Is it Array.formIndex? Or Set.formIndex? We only know at runtime, when we know what Elements is.

Recommended reading: https://medium.com/@venki0119/method-dispatch-in-swift-effects-of-it-on-performance-b5f120e497d3

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • oh, I now read your comment after reading your answer. Okay! `iterator`is **static method**! Right? and actually caused **dynamic method** was `_elements`! Yeah! I now could really understand! thank you so much!! I really am happy to understand that thanks for you. Yeah. time profile showed actually that "`Collection.formIndex`, `Collection.subscript` are **protocol witness** " I didn't careful care about that. Thank you so much. I really appreciate with you my professor. – HyunSu Apr 10 '21 at 14:38
  • @HyunSu “statically dispatched” and “dynamically dispatched”, not “static method” and “dynamic method”. Static methods are something totally different and `dynamic` methods are message dispatched... Be careful with terminology :) – Sweeper Apr 10 '21 at 14:45
  • oops! I was so happy that mistake. thank you for correcting my mistake. – HyunSu Apr 10 '21 at 14:50
  • Hi, my professor, Would you mind If I ask you to answer my [new question](https://stackoverflow.com/q/67124937/11191573)? – HyunSu Apr 16 '21 at 12:19