加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

在Swift中,在循环中声明变量有多糟糕

发布时间:2020-12-14 02:25:20 所属栏目:百科 来源:网络整理
导读:我不知道所有 Swift机制,以及它如何处理变量. 我总是更喜欢在输入for或while循环之前声明变量,而不是语言,而不是反复在循环内声明它们. 但是重新声明变量是不是很糟糕?它会以非常大的迭代次数影响性能吗?具体的Swift如何处理这种行为? 例如: while i 100
我不知道所有 Swift机制,以及它如何处理变量.

我总是更喜欢在输入for或while循环之前声明变量,而不是语言,而不是反复在循环内声明它们.

但是重新声明变量是不是很糟糕?它会以非常大的迭代次数影响性能吗?具体的Swift如何处理这种行为?

例如:

while i < 100 {
  let a = someFunc()
  i += 1
}

VS

let a: MyObj
while i < 100 {
 a = someFunc()
 i += 1
}
这不会影响性能,版本1是首选.即使它会影响性能,您也需要在考虑除了版本1之外的任何其他选项之前在您的精确代码上进行演示.在处理优化编译器时,没有通用的性能答案.做一些与你的代码没有深入探讨的“性能”异常,很可能会让事情变得更糟.正常情况是最优化的情况.

(我知道我夸大了这一点.肯定有办法查看代码并说“这将是非常低效的.”并且有一些古怪的Swift部分,其中看起来不错的东西实际上是坏的,最值得注意的是使用组合字符串,或者使用pre-Swift4 reduce来创建一个数组.但是在那些重要的情况下,你会很快发现它,因为当它们重要时它们真的很糟糕.)

但我们不必猜测任何这一点.我们可以问一下编译器.

// 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
    }
}

首先,请注意我将这些放在一个函数中.这很重要.将它们放在顶层会使一个全局变为一种情况,并且全局变量具有特殊处理,包括线程安全初始化,这使得“外部”情况看起来比在更常规的情况下更昂贵和复杂. (以这种方式正确地测试微优化是非常非常困难的,你可以得出一般的“这是更快”的结论.有很多因素.)

第二次注意打印.我们需要确保以副作用方式使用a,否则优化器可能会完全删除它.打印非常好,即使它很复杂.您也可以使用结果来修改全局,但编译器肯定可以更积极地进行优化,并可能消除我们想要看到的内容. (你真的必须在你关心的实际案例上测试这些东西.)

现在我们可以使用swiftc -O -emit-sil看看Swift将如何处理这些问题.那个-O很关键.很多人试图在不打开优化器的情况下进行性能测试,这些结果都是无意义的.

SIL看起来是什么样的? (Swift中间语言.这是将程序转换为机器代码的第一个重要步骤.如果两个生成相同的SIL,它们将生成相同的机器代码.)

SIL有点长(8000行),所以我要修剪一下.我在<>中的评论.这将变得有点乏味,因为探索这些东西是非常挑剔的.如果你想跳过它,TL-DR是:这两段代码没有区别.不是“一个无关紧要的小差异”.字面上(除了调试器的提示),没有区别.

// 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被定义为函数变量而不是块变量.

知道这两个中缺少什么?对“a”的alloc_stack调用.这是一个整数;它可以放在一个寄存器中.它取决于低级编译器是存储在寄存器还是堆栈中.优化器看到“a”不会逃避代码的这个区域,因此它包含调试器的提示,但它实际上并不打算为它请求存储,甚至不在堆栈上.它可以只取Random的返回寄存器并将其移动到参数寄存器进行打印.由LLVM及其优化器来决定所有这些.

所有这一切的教训是,它对性能来说无关紧要.在可能很重要的模糊情况下(例如当a是全局的)时,版本1将更有效,我认为这与您期望的相反.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读