在swift中哪个循环更快"for"或"for-in"?为什么?

Kri*_*hna 2 swift

当我必须非常了解迭代大型数组所花费的时间时,我应该使用哪个循环.

Air*_*ity 13

简短的回答

不要像这样进行微观优化 - 任何差异都可能远远超过你在循环内执行的操作的速度.如果你真的认为这个循环是一个性能瓶颈,也许你可以通过使用像加速框架这样的东西来获得更好的服务- 但只有当分析显示你的努力才真正值得.

并且不要对抗语言.使用for…in除非你想达到的目标无法表达for…in.这些案件很少见.这样做的好处for…in是难以理解它是错误的.这更重要.优先考虑速度的正确性.清晰度很重要.您甚至可能想要完全跳过for循环并使用mapreduce.

更长的答案

对于数组,如果在没有最快编译器优化的情况下尝试它们,它们的执行方式相同,因为它们基本上执行相同的操作.

大概你的for ;;循环看起来像这样:

var sum = 0
for var i = 0; i < a.count; ++i {
    sum += a[i]
}
Run Code Online (Sandbox Code Playgroud)

你的for…in循环是这样的:

for x in a {
    sum += x
}
Run Code Online (Sandbox Code Playgroud)

让我们重写一下for…in来展示真实情况:

var g = a.generate()
while let x = g.next() {
    sum += x
}
Run Code Online (Sandbox Code Playgroud)

然后让我们重写一下a.generate()返回的内容,以及类似的内容let:

 var g = IndexingGenerator<[Int]>(a)
 var wrapped_x = g.next()
 while wrapped_x != nil {
     let x = wrapped_x!
     sum += x
     wrapped_x = g.next()
 }
Run Code Online (Sandbox Code Playgroud)

以下是IndexingGenerator<[Int]>可能的实现:

struct IndexingGeneratorArrayOfInt {
    private let _seq: [Int]
    var _idx: Int = 0

    init(_ seq: [Int]) {
        _seq = seq
    }

    mutating func generate() -> Int? {
        if _idx != _seq.endIndex {
            return _seq[_idx++]
        }
        else {
            return nil
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

哇,这是很多代码,肯定它比常规for ;;循环执行速度慢!

不.因为虽然这可能是它在逻辑上所做的,但编译器有很多优化的自由度.例如,请注意,IndexingGeneratorArrayOfIntstruct不是a class.这意味着它没有直接声明两个成员变量的开销.这也意味着编译器可能能够内联代码generate- 这里没有间接,没有重载方法和vtable或objc_MsgSend.只是一些简单的指针算术和引用.如果你删除了结构和方法调用的所有语法,你会发现for…in代码最终的内容几乎与for ;;循环所做的完全相同.

for…in 有助于避免性能错误

另一方面,如果对于开头给出的代码,将编译器优化切换到更快的设置,for…in似乎就会for ;;消失.在我运行的一些非科学测试中XCTestCase.measureBlock,总结了大量随机数,它的速度提高了一个数量级.

为什么?由于使用count:

for var i = 0; i < a.count; ++i {
                // ^-- calling a.count every time...
    sum += a[i]
}
Run Code Online (Sandbox Code Playgroud)

也许优化器可以为您修复此问题,但在这种情况下它没有.如果你将不变量拉出来,它会回到与for…in速度相同的方面:

let count = a.count
for var i = 0; i < count; ++i {
    sum += a[i]
}
Run Code Online (Sandbox Code Playgroud)

"哦,我肯定每次都这样做,所以没关系".我说的,真的吗?你确定吗?打赌你有时会忘记.

但是你想要更好的消息吗?与reducefor循环相比,使用(在我的,同样不是非常科学的测试中)进行相同的求和:

let sum = a.reduce(0,+)
Run Code Online (Sandbox Code Playgroud)

但它也更具表现力和可读性(IMO),并允许您let用来声明您的结果.鉴于这应该是你的主要目标,速度是一个额外的奖励.但希望表演会激励你做到这一点.

这仅适用于数组,但其他集合呢?当然这取决于实现,但是有充分的理由相信它对于其他集合(如字典,自定义用户定义集合)会更快.

我的理由是该集合的作者可以实现优化版本generate,因为他们确切知道集合的使用方式.假设下标查找涉及一些计算(例如在数组的情况下指针算法 - 您必须通过值大小添加多个索引,然后将其添加到基指针).在生成的情况下,您知道正在执行的是顺序遍历集合,因此您可以对此进行优化(例如,在数组的情况下,保持指向下一个元素的指针,每次增加时next都是所谓的).同样适用于reduce或的专用成员版本map.

这甚至可能是为什么reduce在数组上表现如此之好 - 谁知道(如果你想尝试找出,你可以在传入的函数上贴一个断点).但这只是使用你可能应该使用的语言结构的另一个理由.


GoZ*_*ner 5

著名的说法是:“我们应该忘记小的效率,大约 97% 的时候都说:过早的优化是万恶之源”Donald Knuth。您似乎不太可能在 %3 中。

专注于手头更大的问题。在它工作之后,如果需要提高性能,那么就担心for循环。但我向你保证,最终,更大的结构效率低下或糟糕的算法选择将是性能问题,而不是循环for

担心for循环是 20 世纪 60 年代的事了。