我总是更喜欢在输入for或while循环之前声明变量,而不是语言,而不是反复在循环内声明它们.@H_404_2@
但是重新声明变量是不是很糟糕?它会以非常大的迭代次数影响性能吗?具体的Swift如何处理这种行为?@H_404_2@
例如:@H_404_2@
while i < 100 { let a = someFunc() i += 1 }
VS@H_404_2@
let a: MyObj while i < 100 { a = someFunc() i += 1 }
(我知道我夸大了这一点.肯定有办法查看代码并说“这将是非常低效的.”并且有一些古怪的Swift部分,其中看起来不错的东西实际上是坏的,最值得注意的是使用组合字符串,或者使用pre-Swift4 reduce来创建一个数组.但是在那些重要的情况下,你会很快发现它,因为当它们重要时它们真的很糟糕.)@H_404_2@
但我们不必猜测任何这一点.我们可以问一下编译器.@H_404_2@
// inside.swift import Foundation func runme() { var i = 0 while i < 100 { let a = Int.random(in: 0...10) print(a) i += 1 } } // outside.swift import Foundation func runme() { var i = 0 var a: Int while i < 100 { a = Int.random(in: 0...10) print(a) i += 1 } }
首先,请注意我将这些放在一个函数中.这很重要.将它们放在顶层会使一个全局变为一种情况,并且全局变量具有特殊处理,包括线程安全初始化,这使得“外部”情况看起来比在更常规的情况下更昂贵和复杂. (以这种方式正确地测试微优化是非常非常困难的,你可以得出一般的“这是更快”的结论.有很多因素.)@H_404_2@
第二次注意打印.我们需要确保以副作用方式使用a,否则优化器可能会完全删除它.打印非常好,即使它很复杂.您也可以使用结果来修改全局,但编译器肯定可以更积极地进行优化,并可能消除我们想要看到的内容. (你真的必须在你关心的实际案例上测试这些东西.)@H_404_2@
现在我们可以使用swiftc -O -emit-sil看看Swift将如何处理这些问题.那个-O很关键.很多人试图在不打开优化器的情况下进行性能测试,这些结果都是无意义的.@H_404_2@
SIL看起来是什么样的? (Swift中间语言.这是将程序转换为机器代码的第一个重要步骤.如果两个生成相同的SIL,它们将生成相同的机器代码.)@H_404_2@
SIL有点长(8000行),所以我要修剪一下.我在<>中的评论.这将变得有点乏味,因为探索这些东西是非常挑剔的.如果你想跳过它,TL-DR是:这两段代码没有区别.不是“一个无关紧要的小差异”.字面上(除了调试器的提示),没有区别.@H_404_2@
// runme() sil hidden @$S4main5runmeyyF : $@convention(thin) () -> () { bb0: ... <define a bunch of variables and function calls> ... <compute the random number and put it in %29> // %19 // user: %49 bb1(%19 : $Builtin.Int64): // Preds: bb5 bb0 %20 = alloc_stack $SystemRandomNumberGenerator // users: %23,%30,%21 store %2 to %20 : $*SystemRandomNumberGenerator // id: %21 br bb2 // id: %22 bb2: // Preds: bb3 bb1 %23 = apply %6<SystemRandomNumberGenerator>(%20,%5) : $@convention(method) <τ_0_0 where τ_0_0 : RandomNumberGenerator> (@inout τ_0_0,@thin UInt.Type) -> UInt // user: %24 %24 = struct_extract %23 : $UInt,#UInt._value // users: %28,%25 %25 = builtin "cmp_ult_Int64"(%24 : $Builtin.Int64,%4 : $Builtin.Int64) : $Builtin.Int1 // user: %26 cond_br %25,bb3,bb4 // id: %26 bb3: // Preds: bb2 br bb2 // id: %27 bb4: // Preds: bb2 %28 = builtin "urem_Int64"(%24 : $Builtin.Int64,%3 : $Builtin.Int64) : $Builtin.Int64 // user: %29 %29 = struct $Int (%28 : $Builtin.Int64) // users: %42,%31 dealloc_stack %20 : $*SystemRandomNumberGenerator // id: %30 < *** Note that %29 is called "a" *** > debug_value %29 : $Int,let,name "a" // id: %31 ... < The print call. This is a lot more code than you think it is...> ... < Add one to i and check for overflow > %49 = builtin "sadd_with_overflow_Int64"(%19 : $Builtin.Int64,%8 : $Builtin.Int64,%13 : $Builtin.Int1) : $(Builtin.Int64,Builtin.Int1) // users: %51,%50 %50 = tuple_extract %49 : $(Builtin.Int64,Builtin.Int1),0 // users: %55,%53 %51 = tuple_extract %49 : $(Builtin.Int64,1 // user: %52 cond_fail %51 : $Builtin.Int1 // id: %52 < Loop if i < 100 > %53 = builtin "cmp_slt_Int64"(%50 : $Builtin.Int64,%1 : $Builtin.Int64) : $Builtin.Int1 // user: %54 cond_br %53,bb5,bb6 // id: %54 bb5: // Preds: bb4 br bb1(%50 : $Builtin.Int64) // id: %55 bb6: // Preds: bb4 %56 = tuple () // user: %57 return %56 : $() // id: %57 } // end sil function '$S4main5runmeyyF'
“外部”代码几乎相同.有什么不同?注意上面代码中的***标记调用debug_value的位置?这在“外部”中缺失,因为a被定义为函数变量而不是块变量.@H_404_2@
知道这两个中缺少什么?对“a”的alloc_stack调用.这是一个整数;它可以放在一个寄存器中.它取决于低级编译器是存储在寄存器还是堆栈中.优化器看到“a”不会逃避代码的这个区域,因此它包含调试器的提示,但它实际上并不打算为它请求存储,甚至不在堆栈上.它可以只取Random的返回寄存器并将其移动到参数寄存器进行打印.由LLVM及其优化器来决定所有这些.@H_404_2@
所有这一切的教训是,它对性能来说无关紧要.在可能很重要的模糊情况下(例如当a是全局的)时,版本1将更有效,我认为这与您期望的相反.@H_404_2@
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。