Air*_*ity 13
不要像这样进行微观优化 - 任何差异都可能远远超过你在循环内执行的操作的速度.如果你真的认为这个循环是一个性能瓶颈,也许你可以通过使用像加速框架这样的东西来获得更好的服务- 但只有当分析显示你的努力才真正值得.
并且不要对抗语言.使用for…in除非你想达到的目标无法表达for…in.这些案件很少见.这样做的好处for…in是难以理解它是错误的.这更重要.优先考虑速度的正确性.清晰度很重要.您甚至可能想要完全跳过for循环并使用map或reduce.
对于数组,如果在没有最快编译器优化的情况下尝试它们,它们的执行方式相同,因为它们基本上执行相同的操作.
大概你的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 ;;循环执行速度慢!
不.因为虽然这可能是它在逻辑上所做的,但编译器有很多优化的自由度.例如,请注意,IndexingGeneratorArrayOfInt这struct不是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在数组上表现如此之好 - 谁知道(如果你想尝试找出,你可以在传入的函数上贴一个断点).但这只是使用你可能应该使用的语言结构的另一个理由.
著名的说法是:“我们应该忘记小的效率,大约 97% 的时候都说:过早的优化是万恶之源”Donald Knuth。您似乎不太可能在 %3 中。
专注于手头更大的问题。在它工作之后,如果需要提高性能,那么就担心for循环。但我向你保证,最终,更大的结构效率低下或糟糕的算法选择将是性能问题,而不是循环for。
担心for循环是 20 世纪 60 年代的事了。
| 归档时间: |
|
| 查看次数: |
4144 次 |
| 最近记录: |