通过 Swift 中的计算属性访问静态 属性 是否效率低下?
Is It Inefficient To Access A Static Property Through Computed Properties In Swift?
假设我有一些常量应该被视为 class 变量。
static let constant1 = 1
static let constant2 = 2
static let constant3 = 3
为了访问 constant1
,我需要通过 class,例如 OwnerClass.constant1
。
但我也可以通过计算属性访问它们。例如
var constant1:Int { get{ return OwnerClass.constant1 }}
这样做是为了避免重复输入 OwnerClass.
。
但问题是,这样效率低吗?
与大多数优化问题一样,这取决于您的精确代码和编译器的版本,而且我们不必猜测,我们可以检查。
我假设您所说的“低效”是指“无法内联访问器调用”。
TL;DR 是:在几乎所有情况下,优化器都会以任何一种方式内联。存在访问器版本未内联的极端情况,例如,如果调用者位于 top-level(不在函数内部)并且 class 是 non-final。 (我不知道为什么那是 corner-case;它可能是一个优化器错误。)
我对这是否是一个好的设计持中立态度。我对此很好(我自己偶尔也会使用这种模式)。但我当然不会出于性能考虑而避免它。 (如果一个额外的函数调用会成为问题,您将需要 hand-optimize 无论如何。)
详情
与大多数 optimization/performance 问题一样,这将取决于您的确切代码和编译器的版本。正如我所说,有一些极端情况没有得到优化。我用 Swift 5.5.2.
测试过
首先,我创建了一个测试程序:
// Avoid the complexity around calling print()
// while ensuring that the variable is not optimized away
@inline(never)
func handle(_ x: Int) {
print(x)
}
// Stick it in a function to avoid the special handling of
// global variables
func f() {
let c = OwnerClass()
let x = OwnerClass.constant1
handle(x)
let y = c.constant1
handle(y)
}
// Make sure to call the function so it's not optimized away
f()
然后我用几个版本的 OwnerClass 检查它(我使用 12345678 以便在输出中更容易找到它):
// Class
class OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}
// Final class
final class OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}
// Struct
struct OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}
// Instance constant
class OwnerClass {
static let constant1 = 12345678
let constant1:Int = OwnerClass.contant1
}
唯一遇到麻烦的(例如,当我没有将其全部包装在一个函数中时)是带有访问器的 non-final class。
为了查看优化器的作用,我使用 swiftc -emit-sil -O x.swift
进行了编译。在所有情况下,这就是 f()
编译为:
// f()
sil hidden @$s1x1fyyF : $@convention(thin) () -> () {
bb0:
%0 = integer_literal $Builtin.Int64, 12345678 // user: %1
%1 = struct $Int (%0 : $Builtin.Int64) // users: %6, %5, %4, %2
debug_value %1 : $Int, let, name "x" // id: %2
// function_ref handle(_:)
%3 = function_ref @$s1x6handleyySiF : $@convention(thin) (Int) -> () // users: %6, %4
%4 = apply %3(%1) : $@convention(thin) (Int) -> ()
debug_value %1 : $Int, let, name "y" // id: %5
%6 = apply %3(%1) : $@convention(thin) (Int) -> ()
%7 = tuple () // user: %8
return %7 : $() // id: %8
} // end sil function '$s1x1fyyF'
需要注意的重要一点是常量 12345678 作为 %0 内联到函数中(包装到 %1 中),然后在 %4 和 %6 中两次使用它来调用 handle()
。没有调用访问者。 OwnerClass 甚至没有被引用(c
的创建被优化掉了)。
假设我有一些常量应该被视为 class 变量。
static let constant1 = 1
static let constant2 = 2
static let constant3 = 3
为了访问 constant1
,我需要通过 class,例如 OwnerClass.constant1
。
但我也可以通过计算属性访问它们。例如
var constant1:Int { get{ return OwnerClass.constant1 }}
这样做是为了避免重复输入 OwnerClass.
。
但问题是,这样效率低吗?
与大多数优化问题一样,这取决于您的精确代码和编译器的版本,而且我们不必猜测,我们可以检查。
我假设您所说的“低效”是指“无法内联访问器调用”。
TL;DR 是:在几乎所有情况下,优化器都会以任何一种方式内联。存在访问器版本未内联的极端情况,例如,如果调用者位于 top-level(不在函数内部)并且 class 是 non-final。 (我不知道为什么那是 corner-case;它可能是一个优化器错误。)
我对这是否是一个好的设计持中立态度。我对此很好(我自己偶尔也会使用这种模式)。但我当然不会出于性能考虑而避免它。 (如果一个额外的函数调用会成为问题,您将需要 hand-optimize 无论如何。)
详情
与大多数 optimization/performance 问题一样,这将取决于您的确切代码和编译器的版本。正如我所说,有一些极端情况没有得到优化。我用 Swift 5.5.2.
测试过首先,我创建了一个测试程序:
// Avoid the complexity around calling print()
// while ensuring that the variable is not optimized away
@inline(never)
func handle(_ x: Int) {
print(x)
}
// Stick it in a function to avoid the special handling of
// global variables
func f() {
let c = OwnerClass()
let x = OwnerClass.constant1
handle(x)
let y = c.constant1
handle(y)
}
// Make sure to call the function so it's not optimized away
f()
然后我用几个版本的 OwnerClass 检查它(我使用 12345678 以便在输出中更容易找到它):
// Class
class OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}
// Final class
final class OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}
// Struct
struct OwnerClass {
static let constant1 = 12345678
var constant1:Int { get{ return OwnerClass.constant1 }}
}
// Instance constant
class OwnerClass {
static let constant1 = 12345678
let constant1:Int = OwnerClass.contant1
}
唯一遇到麻烦的(例如,当我没有将其全部包装在一个函数中时)是带有访问器的 non-final class。
为了查看优化器的作用,我使用 swiftc -emit-sil -O x.swift
进行了编译。在所有情况下,这就是 f()
编译为:
// f()
sil hidden @$s1x1fyyF : $@convention(thin) () -> () {
bb0:
%0 = integer_literal $Builtin.Int64, 12345678 // user: %1
%1 = struct $Int (%0 : $Builtin.Int64) // users: %6, %5, %4, %2
debug_value %1 : $Int, let, name "x" // id: %2
// function_ref handle(_:)
%3 = function_ref @$s1x6handleyySiF : $@convention(thin) (Int) -> () // users: %6, %4
%4 = apply %3(%1) : $@convention(thin) (Int) -> ()
debug_value %1 : $Int, let, name "y" // id: %5
%6 = apply %3(%1) : $@convention(thin) (Int) -> ()
%7 = tuple () // user: %8
return %7 : $() // id: %8
} // end sil function '$s1x1fyyF'
需要注意的重要一点是常量 12345678 作为 %0 内联到函数中(包装到 %1 中),然后在 %4 和 %6 中两次使用它来调用 handle()
。没有调用访问者。 OwnerClass 甚至没有被引用(c
的创建被优化掉了)。