在 Swift 中通过计算属性访问静态属性效率低吗?

发布于 2025-01-10 09:15:14 字数 380 浏览 1 评论 0原文

假设我有一些应该被视为类变量的常量。

static let constant1 = 1
static let constant2 = 2
static let constant3 = 3

为了访问 constant1,我需要遍历该类,例如 OwnerClass.constant1

但我也可以通过计算属性访问它们。例如,

var constant1:Int { get{ return OwnerClass.constant1 }}

这样做是为了避免重复输入 OwnerClass.

但问题是,这样效率低吗?

Say I have some constants that should be treated as class variables.

static let constant1 = 1
static let constant2 = 2
static let constant3 = 3

In order to access constant1, I need to go thru the class, like OwnerClass.constant1.

But I can also access them thru computed properties. For example

var constant1:Int { get{ return OwnerClass.constant1 }}

This is done to avoid the need to type OwnerClass. repetitively.

But the question is, is that inefficient?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梅窗月明清似水 2025-01-17 09:15:14

与大多数优化问题一样,这取决于您的精确代码和编译器的版本,而且我们不必猜测,我们可以检查。

我所说的“效率低下”是指“无法内联访问器调用”。

TL;DR 是:在几乎所有情况下,优化器都会以任何一种方式内联它。在某些特殊情况下,访问器版本不是内联的,例如,如果调用者位于顶层(不在函数内部)并且该类不是最终类。 (我不知道为什么这是一个极端情况;它可能是一个优化器错误。)

我对这是否是一个好的设计持中立态度。我对此很满意(我自己偶尔也会使用这种模式)。但我当然不会出于性能考虑而避免它。 (如果一个额外的函数调用会成为问题,无论如何您都需要手动优化。)

细节

与大多数优化/性能问题一样,这将取决于您的确切代码和编译器的版本。正如我所说,在某些极端情况下,这一点没有得到优化。我用 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
}

唯一遇到问题的程序(例如,当我没有包装它时)全部在一个函数中),是带有访问器的非最终类。

为了了解优化器的作用,我使用 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 的创建被优化掉了)。

As with most optimization questions, it depends on your precise code and the version of the compiler, and also we don't have to guess, we can check.

By "inefficient" I'm going to assume you mean "fails to inline the accessor call."

The TL;DR is: In almost all cases the optimizer will inline this either way. There are corner cases where the accessor version is not inlined, for example if the caller is at the top-level (not inside a function) and the class is non-final. (I don't know why that's a corner-case; it may be an optimizer bug.)

I'm neutral on whether this is a good design. I'm fine with it (and occasionally use this pattern myself). But I certainly wouldn't avoid it out of performance concerns. (In cases where one extra function call would be a problem, you're going to need to hand-optimize anyway.)

The details

As with most optimization/performance questions, it will depend on your exact code and the version of the compiler. As I said, there are some corner cases where this doesn't get optimized. I tested with Swift 5.5.2.

First, I created a test program:

// 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()

Then I checked it with several version of OwnerClass (I use 12345678 to make it easier to find in the output):

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

The only one that ever had trouble (for example, when I didn't wrap it all in a function), was the non-final class with an accessor.

To see what the optimizer does, I compiled with swiftc -emit-sil -O x.swift. In all cases, this is what f() compiles to:

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

The important thing to note is that the constant 12345678 is inlined into the function as %0 (wrapped into %1), and then it's used twice in %4 and %6 to call handle(). No calls are made to the accessor. OwnerClass isn't even referenced (the creation of c is optimized away).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文