I'm working on a performance critical section, and it would be very useful to implement a wrapper pattern like so:
protocol MyProtocol {
var someValue: Int { get }
}
struct Wrapped: MyProtocol {
var someValue: Int
}
struct Wrapper: MyProtocol {
var wrapped: Wrapped
var someValue: Int {
return wrapped.someValue
}
}
So I can imagine that the compiler could optimize the access on Wrapper.someValue
so that it would be equivalent to Wrapped.someValue
since in either case this is essentially just a memory access at a fixed offset in the struct, but is this actually the case in practice, or is there additional overhead from this type of wrapping?
This depends on a few things. Your example (not public
s) seem to suggest that this all happens within one module. If that is the case, then you can get the Swift compiler to emit just a memory load. The only thing you need to do is use it as a generic (and not as an existential). So for example in this example:
protocol MyProtocol {
var someValue: Int { get }
}
struct Wrapped: MyProtocol {
var someValue: Int
}
struct Wrapper: MyProtocol {
var wrapped: Wrapped
var someValue: Int {
return wrapped.someValue
}
}
@inline(never) // Just to actually get this method emitted
func testStuff<W: MyProtocol>(_ w: W) -> Int {
return w.someValue &+ 0xdead
}
the disassembly (needs to be compiled with optimisations) is:
generic specialization <test.Wrapper> of test.testStuff<A where A: test.MyProtocol>(A) -> Swift.Int:
0000000100000d50 pushq %rbp
0000000100000d51 movq %rsp, %rbp
0000000100000d54 leaq 0xdead(%rdi), %rax // this is the load & the add
0000000100000d5b popq %rbp
0000000100000d5c retq
If you want to achieve the same across modules, so for example if it were public func testStuff<W: MyProtocol>(...)
, then you would either need to make it @inlinable public func testStuff<...>(...)
and mark a few more things as @inlinable
/@usableFromInline
or compile with Swift 5.2's new -cross-module-optimisation
switch
.
3 Likes