我不了解Swift的所有机制,也不了解它如何处理变量。
我总是更喜欢在进入for
或while
循环之前声明变量,而与语言无关,而不是一遍又一遍地在循环中声明它们。
但是重新声明变量是否很糟糕?迭代是否会影响性能? 特别是Swift 如何处理此行为?
示例:
while i < 100 {
let a = someFunc()
i += 1
}
VS
let a: MyObj
while i < 100 {
a = someFunc()
i += 1
}
答案 0 :(得分:6)
Swift可以像大多数语言一样处理它。局部变量在堆栈上声明,并在退出定义它们的作用域时从堆栈中弹出。压入和弹出堆栈是一种非常低成本的操作。
Swift使用的LLVM编译器使用了相当高级的代码优化,尤其是在发布模式下。在您的简单示例中,您正在使用的变量可能会被优化,因为它们实际上并没有用于任何事情。
总而言之,两个变量之间没有明显的性能差异,第一个将变量放入循环中的方法更加简洁,如rmaddy在其评论中所述。最好在尽可能狭窄的范围内定义变量。它显示了您对变量的意图,并避免了意外的后果。
答案 1 :(得分:6)
这不会影响性能,因此高度推荐使用版本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
在一种情况下成为全局变量,并且全局变量具有特殊处理,包括线程安全的初始化,这使“外部”情况比在更常规的用法中看起来更加昂贵和复杂。 。 (很难以一种可以得出一般的“更快”结论的方式正确地测试微优化,这是非常困难的。)
第二个注意print
。我们需要确保以副作用的方式使用a
,否则优化程序可能会完全删除它。 print
非常好,即使很复杂。您还可以使用结果来修改全局变量,但是编译器肯定可以更加积极地优化它,并且可以消除我们希望看到的内容。 (您真的 真的必须在您关心的实际情况下测试这些东西。)
现在,我们可以使用swiftc -O -emit-sil
来了解Swift将如何处理这些问题。 -O
是至关重要的。如此多的人试图在不打开优化器的情况下进行性能测试,而这些结果毫无意义。
那么SIL是什么样的? (快速中级语言。这是将程序转换为机器代码的第一步。如果两件事生成相同的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, Builtin.Int1), 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
被定义为函数变量而不是块变量,所以在”外部”中缺少该变量。
知道这两个都缺少什么? alloc_stack
调用“ a”。这是一个整数;它可以放在寄存器中。无论是将其存储在寄存器还是堆栈中,都取决于较低级别的编译器。优化器发现“ a”不会转义代码的此区域,因此它包含调试器的提示,但实际上并不需要为它进行存储,甚至不在堆栈上。它只需要获取Random
的返回寄存器并将其移至print
的参数寄存器。这一切取决于LLVM及其优化程序。
这一切的教训是,它实际上与性能无关紧要。在不太重要的情况下(例如a
是全局的),版本1会更有效,我认为这与您的预期相反。