好奇的是,它在swift中效率更高/更好:
通过一个例子可能更好地解释了这一点:
var one = Object()
var two = Object()
var three = Object()
func firstFunction() {
let tempVar1 = //calculation1
one = tempVar1
let tempVar2 = //calculation2
two = tempVar2
let tempVar3 = //calculation3
three = tempVar3
}
func seconFunction() {
var tempVar = //calculation1
one = tempVar
tempVar = //calculation2
two = tempVar
tempVar = //calculation3
three = tempVar
}
这两个功能中哪一个更有效?谢谢你的时间!
答案 0 :(得分:3)
除非您处理非常专业的用例,否则这应该永远不会产生有意义的性能差异。
编译器可能很容易简化事件以指导firstFunction
中的分配,我不确定secondFunction
是否容易适用于类似的编译器优化。您可能必须是编译器的专家或进行一些性能测试才能找到任何差异。
无论如何,除非你以数十万或数百万的规模做到这一点,否则不用担心。
我个人认为以secondFunction
的方式重复使用变量是不必要的混淆,但对每个人来说都是如此。
注意:看起来你正在处理类,但要注意struct
复制语义意味着重用变量无论如何都是无用的。
答案 1 :(得分:3)
不要太可爱,但上面代码中最有效的版本是:
var one = Object()
var two = Object()
var three = Object()
这在逻辑上等同于您编写的所有代码,因为您从未使用过计算结果(假设计算没有副作用)。优化器的工作就是采用这种最简单的形式。从技术上讲,最简单的形式是:
func main() {}
但优化器并非 智能。但优化器确实 足够聪明,可以进入我的第一个例子。考虑一下这个程序:
var one = 1
var two = 2
var three = 3
func calculation1() -> Int { return 1 }
func calculation2() -> Int { return 2 }
func calculation3() -> Int { return 3 }
func firstFunction() {
let tempVar1 = calculation1()
one = tempVar1
let tempVar2 = calculation2()
two = tempVar2
let tempVar3 = calculation3()
three = tempVar3
}
func secondFunction() {
var tempVar = calculation1()
one = tempVar
tempVar = calculation2()
two = tempVar
tempVar = calculation3()
three = tempVar
}
func main() {
firstFunction()
secondFunction()
}
通过编译器运行优化:
$ swiftc -O -wmo -emit-assembly x.swift
这是整个输出:
.section __TEXT,__text,regular,pure_instructions
.macosx_version_min 10, 9
.globl _main
.p2align 4, 0x90
_main:
pushq %rbp
movq %rsp, %rbp
movq $1, __Tv1x3oneSi(%rip)
movq $2, __Tv1x3twoSi(%rip)
movq $3, __Tv1x5threeSi(%rip)
xorl %eax, %eax
popq %rbp
retq
.private_extern __Tv1x3oneSi
.globl __Tv1x3oneSi
.zerofill __DATA,__common,__Tv1x3oneSi,8,3
.private_extern __Tv1x3twoSi
.globl __Tv1x3twoSi
.zerofill __DATA,__common,__Tv1x3twoSi,8,3
.private_extern __Tv1x5threeSi
.globl __Tv1x5threeSi
.zerofill __DATA,__common,__Tv1x5threeSi,8,3
.private_extern ___swift_reflection_version
.section __TEXT,__const
.globl ___swift_reflection_version
.weak_definition ___swift_reflection_version
.p2align 1
___swift_reflection_version:
.short 1
.no_dead_strip ___swift_reflection_version
.linker_option "-lswiftCore"
.linker_option "-lobjc"
.section __DATA,__objc_imageinfo,regular,no_dead_strip
L_OBJC_IMAGE_INFO:
.long 0
.long 1088
您的功能在输出中甚至,因为他们不做任何事情。 main
简化为:
_main:
pushq %rbp
movq %rsp, %rbp
movq $1, __Tv1x3oneSi(%rip)
movq $2, __Tv1x3twoSi(%rip)
movq $3, __Tv1x5threeSi(%rip)
xorl %eax, %eax
popq %rbp
retq
将值1,2和3粘贴到全局变量中,然后退出。
我的观点是,如果它足够聪明,不要尝试用临时变量进行二次猜测。它的工作就是明白这一点。事实上,让我们看看它有多聪明。我们将关闭整个模块优化(-wmo
)。如果没有它,它就不会剥离这些功能,因为它不知道其他什么东西会调用它们。然后我们就可以看到它是如何编写这些函数的。
此处firstFunction()
:
__TF1x13firstFunctionFT_T_:
pushq %rbp
movq %rsp, %rbp
movq $1, __Tv1x3oneSi(%rip)
movq $2, __Tv1x3twoSi(%rip)
movq $3, __Tv1x5threeSi(%rip)
popq %rbp
retq
因为它可以看到计算方法只返回常量,所以它会内联这些结果并将它们写入全局变量。
现在如何secondFunction()
:
__TF1x14secondFunctionFT_T_:
pushq %rbp
movq %rsp, %rbp
popq %rbp
jmp __TF1x13firstFunctionFT_T_
是。这很聪明。它意识到secondFunction()
与firstFunction()
相同,它只是跳转到它。你的函数字面上不可能更相同,优化器就知道了。
那么效率最高的是什么?最容易推理的那个。副作用最少的那个。最容易阅读和调试的那个。这是你应该关注的效率。让优化器完成它的工作。它非常聪明。你用更好,更清晰,更明显的Swift写的越多,优化器就越容易完成它的工作。每次你做一些聪明的事情"为了表现,"你只是让优化器更加努力地弄清楚你做了什么(并且可能撤消它)。
完成这个想法:你创建的局部变量几乎没有给编译器提示。编译器在将代码转换为内部表示(IR)时会生成自己的局部变量。 IR位于static single assignment form(SSA),其中每个变量只能分配一次。因此,您的第二个函数实际上会创建比第一个函数更多的局部变量。这是函数1(使用swiftc -emit-ir x.swift
创建):
define hidden void @_TF1x13firstFunctionFT_T_() #0 {
entry:
%0 = call i64 @_TF1x12calculation1FT_Si()
store i64 %0, i64* getelementptr inbounds (%Si, %Si* @_Tv1x3oneSi, i32 0, i32 0), align 8
%1 = call i64 @_TF1x12calculation2FT_Si()
store i64 %1, i64* getelementptr inbounds (%Si, %Si* @_Tv1x3twoSi, i32 0, i32 0), align 8
%2 = call i64 @_TF1x12calculation3FT_Si()
store i64 %2, i64* getelementptr inbounds (%Si, %Si* @_Tv1x5threeSi, i32 0, i32 0), align 8
ret void
}
在此表单中,变量具有%
前缀。如你所见,有3个。
这是你的第二个功能:
define hidden void @_TF1x14secondFunctionFT_T_() #0 {
entry:
%0 = alloca %Si, align 8
%1 = bitcast %Si* %0 to i8*
call void @llvm.lifetime.start(i64 8, i8* %1)
%2 = call i64 @_TF1x12calculation1FT_Si()
%._value = getelementptr inbounds %Si, %Si* %0, i32 0, i32 0
store i64 %2, i64* %._value, align 8
store i64 %2, i64* getelementptr inbounds (%Si, %Si* @_Tv1x3oneSi, i32 0, i32 0), align 8
%3 = call i64 @_TF1x12calculation2FT_Si()
%._value1 = getelementptr inbounds %Si, %Si* %0, i32 0, i32 0
store i64 %3, i64* %._value1, align 8
store i64 %3, i64* getelementptr inbounds (%Si, %Si* @_Tv1x3twoSi, i32 0, i32 0), align 8
%4 = call i64 @_TF1x12calculation3FT_Si()
%._value2 = getelementptr inbounds %Si, %Si* %0, i32 0, i32 0
store i64 %4, i64* %._value2, align 8
store i64 %4, i64* getelementptr inbounds (%Si, %Si* @_Tv1x5threeSi, i32 0, i32 0), align 8
%5 = bitcast %Si* %0 to i8*
call void @llvm.lifetime.end(i64 8, i8* %5)
ret void
}
这个有6个局部变量!但是,就像原始源代码中的局部变量一样,这也没有告诉我们最终的性能。编译器只是创建这个版本,因为它比变量可以更改其值的版本更容易推理(并因此优化)。
(更为引人注目的是SIL(-emit-sil
)中的代码,它为函数1创建了16个局部变量,为函数2创建了17个!如果编译器很乐意发明16个局部变量让它更容易推理大约6行代码,你当然不应该担心你创建的局部变量。他们不仅仅是一个小问题;他们完全免费。)< / p>
答案 2 :(得分:2)
你应该只是内联局部变量:
var one: Object
var two: Object
var three: Object
func firstFunction() {
one = //calculation1
two = //calculation2
three = //calculation3
}
这样做的一个例外是你最终写下这样的东西:
var someOptional: Foo?
func init() {
self.someOptional = Foo()
self.someOptional?.a = a
self.someOptional?.b = b
self.someOptional?.c = c
}
在这种情况下,最好这样做:
func init() {
let foo = Foo()
foo.a = a
foo.b = b
foo.c = c
self.someOptional = foo
}
或者也许:
func init() {
self.someOptional = {
let foo = Foo()
foo.a = a
foo.b = b
foo.c = c
return foo
}()
}