Description
Previous ID | SR-11706 |
Radar | rdar://problem/58667192 |
Original Reporter | @Lukasa |
Type | Bug |
Status | Reopened |
Resolution |
Attachment: Download
Environment
Apple Swift version 5.1.1 (swiftlang-1100.0.275.2 clang-1100.0.33.9)
Target: x86_64-apple-darwin19.0.0
Additional Detail from JIRA
Votes | 1 |
Component/s | Compiler |
Labels | Bug, Performance |
Assignee | @gottesmm |
Priority | Medium |
md5: fe768e083fde0dd0bb93966e9182970c
Issue Description:
Consider a SwiftPM package that defines two modules. The first is called testlib
, and contains the following code:
public struct Foo {
private var _storage: _Storage
public var bar: Int {
return 5
}
public init() {
self._storage = _Storage()
}
}
extension Foo {
fileprivate class _Storage { }
}
The second module is an executable called test
, which is defined like so:
import Foundation
import testlib
@inline(never)
func main(_ f: inout Foo) -> Int {
let result = f.bar
return result
}
var f = Foo()
let result = main(&f)
if result != 5 {
exit(1)
} else {
exit(0)
}
This program creates a Foo
, passes it inout
to a function, and then calls a non mutating
get accessor on Foo
. This code performs some excessive retains/releases in test.main
, which when disassembled looks like this:
_$s4test4mainySi7testlib3FooVzF: // test.main(inout testlib.Foo) -> Swift.Int
push rbp ; CODE XREF=_main+55
mov rbp, rsp
push r14
push rbx
mov rbx, qword [rdi]
mov rdi, rbx ; argument "instance" for method imp___stubs__swift_retain
call imp___stubs__swift_retain ; swift_retain
mov rdi, rbx
call _$s7testlib3FooV3barSivg ; testlib.Foo.bar.getter : Swift.Int
mov r14, rax
mov rdi, rbx ; argument "instance" for method imp___stubs__swift_release
call imp___stubs__swift_release ; swift_release
mov rax, r14
pop rbx
pop r14
pop rbp
ret
In this case, a swift_retain
is performed before the invocation of the computed property.
This seems excessive: it seems like the natural semantic for a nonmutating get accessor would be to assume that the reference currently held by the calling function ought to be sufficient to cover the computing needs of that accessor. I'd have assumed, then, that nonmutating calls should be passed self at +0
and can retain self if they need to.
The result of this is that repeated calls to computed properties incur substantial ARC traffic. For example, if we called Foo.bar
in a loop, we will retain/release once per loop invocation.
In the case of a computed property as simple as Foo.bar
the cost of these retain/release operations will strictly dominate the actual calculation of the computed property.
This is non-theoretical: SwiftNIO issue 1218 covers a feature request to have SwiftNIO add an "unsafe" equivalent of its ByteBuffer type to elide refcounting overhead in a parse-heavy loop. The ARC overhead here came entirely from accessing non-inlinable computed properties: making them inlinable halved the runtime of the program and elided the ARC traffic almost entirely. It would be good if we didn't have to make these properties inlinable to allow the compiler to see that they don't need self at +1
.
I've attached a sample project.