通过 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 的创建被优化掉了)。