关于swift:理解-Swift-中的-inlinable-译

6次阅读

共计 2487 个字符,预计需要花费 7 分钟才能阅读完成。

@inlinable 属性是 Swift 鲜为人知的属性之一。与其余同类一样,它的目标是启用一组特定的微优化,您能够应用它们来进步应用程序的性能。让咱们来看看这个是如何工作的。

在 Swift 中应用 @inline 进行内联扩大

兴许最须要留神的是,尽管 @inlinable 与代码内联无关,但它与咱们之前曾经介绍过的 @inline 属性不同。然而为了防止您不得不浏览两篇文章,咱们将在介绍 @inlinable 之前再次介绍这些概念。

在编程中,内联扩大,也称为内联,是一种编译器优化技术,它用所述办法的主体替换办法调用。

调用办法的操作很难做到没有性能开销。正如咱们在对于内存调配的文章中所述,当应用程序心愿将新的堆栈跟踪推送到线程时,会进行大量的编排来传输、存储和扭转应用程序的状态。尽管从方面来说,堆栈跟踪能够简化调试过程,但您可能想晓得是否有必要每次都这样做。如果一个办法太简略,调用它的开销可能不仅是不必要的,而且对应用程序的整体性能无害:

func printPlusOne(_ num: Int) {print("My number: \(num + 1)")
}

print("I'm going to print some numbers!")
printPlusOne(5)
printPlusOne(6)
printPlusOne(7)
print("Done!")

printPlusOne 这样的办法过于简略,无奈在应用程序的二进制文件中证实残缺的定义。只是为了清晰起见,咱们在代码中定义了它,但在公布这个应用程序时最好去掉它,用残缺的实现替换所有调用它的中央,如下所示:

print("I'm going to print some number!")
print("My number: \(5 + 1)")
print("My number: \(6 + 1)")
print("My number: \(7 + 1)")
print("Done!")

这种缩小办法调用开销,可能会进步应用程序的性能,但可能会减少整体包大小,具体取决于被内联的办法的大小 (能够了解为替换,被替换的办法中的代码多,天然代码量就多了)。这个过程是由 Swift 编译器主动实现的,依据你工程构建时的优化级别,水平是可变的。@inline 属性能够用来疏忽优化级别,强制编译器遵循特定的方向。内联也能够用于混同。

@inlinable 的目标是什么?

大多数优化,如内联,大多在外部实现。尽管能够保障本人正在开发的模块将失去适当的优化,但当咱们解决来自其余模块的调用时,事件会简单得多。

编译器优化之所以产生,是因为编译器对正在编译的内容有残缺的理解,但在构建 framework 时,编译器不可能晓得导入方将如何应用他。因而尽管框架的外部代码将失去优化,但公共接口很可能会不变。

咱们可能会想,咱们能够通知编译器组成 framework 的源信息,以便它可能从新取得 framework 的信息,并对其优化。但当咱们意识到该框架的导入器正在链接的是曾经编译的货色时,这会变得复杂。源文件上的所有信息都不见了,如果这是第三方框架,甚至可能一开始就没有这些源信息。(拿不到 framework 所有的源信息)

这不是一个不可能呈现的问题,尽管编译器有很多种解决这个问题的办法,但实质是一样的,就是让模块的公共接口在链接时蕴含更多编译器可用的信息,这样就能够进一步优化 framework 中的代码,不限于内联。

在实践中,你可能会留神到这样做可能会重大失控。如果咱们给公共接口的每个办法都增加信息,这样会让框架的体积增大,而且大部分都会被节约掉。咱们不晓得 framework 将会被如何应用,所以不能一概而论的这样操作。

Swift 会让你本人决定来解决这种问题。@inlinable 属性容许您为特定办法启用跨模块内联。这样做了之后,办法的实现将作为模块公共接口的一部分来公开,容许编译器进一步优化来自不同模块的调用。

@inlinable func printPlusOne(_ num: Int) {print("My number: \(num + 1)")
}

如果内联办法恰好在外部调用其余办法,编译器会要求您也公开这些办法。这能够通过将它们标记为 @inlinable@usableFromInline@usableFromInline 表明尽管该办法在内联办法中应用的,但它自身并不真正应该被内联。

@inlinable func myMethod() {myMethodHelper()
}

// Used within an inlinable method, but not inlinable itself
@usableFromInline internal func myMethodHelper() {// ...}

应用 @inlinable 最大的收益就是有些办法可能会有性能开销,只管大多数的办法这种开销能够忽略不计,但像那种蕴含泛型和必报的,开销很大。

@inlinable public func allEqual<T>(_ seq: T) -> Bool where T : Sequence, T.Element : Equatable {// ...}

编译器曾经利用了多种办法来解决泛型的问题。但正如咱们说的,在独自的模块内调用,这些办法并不实用。这种状况下,应用 @inlinable 能够带来性能晋升,不过包的大小会减少。

另一方面,当你构建时,@inlinable 可能有大问题。一个被标记为 @inlinable 的办法实现有扭转时,除非从新编译,要不然导入他的模块无奈应用他的批改。通常你能够通过简略地替换二进制文件来更新框架,但因为某些办法的实现被内联,即便您链接到新版本,应用程序仍将持续运行旧的版本。因而,启用 library evolution 设置的应用程序可能会发现自己无奈应用 @inlinable,因为这可能会毁坏框架的 ABI 稳定性。

应该应用 @inlinable 还是 @inline?

除非您正在构建一个具备 ABI/API 稳定性的框架,否则这些属性应该是齐全平安的。不过,我强烈建议你不要应用它们,除非你晓得本人在做什么。它们是为在大多数应用程序都无奈体验的十分非凡的状况下应用而构建的。

正文完
 0