protocol P {
var id: Int { get set }
}
extension P {
init() {
self.id = 1
}
}
struct S: P {
var id: Int
}
results in
bla.swift:16:25: error: 'self' used before 'self.init' call or assignment to 'self'
self.id = 1
^
bla.swift:17:9: error: 'self.init' isn't called on all paths before returning from initializer
}
Is there a way to omit this error?
I'm trying to get rid of some classes in a hot path and I have to compare to structs:
let a = S(... other members...)
let b = S(... other members...)
if a != b { ... }
Without more details it's not obvious what's wrong with this (just not having that protocol extension):
protocol P: Equatable {
var id: Int { get set }
}
struct S: P {
var id: Int
var x: Double
}
func foo() {
let a = S(id: 1, x: 2)
let b = S(id: 1, x: 3)
if a != b {
...
}
}
bla.swift:31:35: remark: heap allocated ref of type 'Lambert'
bsdf.bxdfs.append(Lambert())
^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:32:22: remark: retain of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:32:22: remark: retain of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:32:22: remark: retain of type 'BxDF'
bsdf.sample()
^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:32:22: remark: retain of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:32:22: remark: retain of type 'BxDF'
bsdf.sample()
^
bla.swift:32:29: remark: release of type 'BxDF'
bsdf.sample()
^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:32:29: remark: release of type 'BxDF'
bsdf.sample()
^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:32:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
I'm trying to get rid of all ARC traffic because this is the hot loop and kills performance.
The above example was one attempt to replace classes with structs and adding identity via an id counter.
I'm open for better ways.
protocol BxDF {
func evaluate()
}
struct Lambert: BxDF {
func evaluate() {}
}
struct BSDF {
func sample() {
let i = Int.random(in: 0..<bxdfs.count)
for j in 0..<bxdfs.count { if i != j { bxdfs[j].evaluate() } }
}
var bxdfs = [BxDF]()
}
@_semantics("optremark")
func main() {
for _ in 0..<1_000_000 {
var bsdf = BSDF()
bsdf.bxdfs.append(Lambert())
bsdf.sample()
}
}
main()
which leaves me with
bla.swift:24:22: remark: retain of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
^
bla.swift:24:29: remark: release of type '__ContiguousArrayStorageBase'
bsdf.sample()
And then one wishes for a stack-based small Array.
Do you have a limit on array length and how big is this limit? In the above example arrays are 1-element long but perhaps it is an oversimplified version of real code. If it is up to a reasonably small number and only a few operations are needed (like get count and element at index) I'd invest into some simple fixed-sized array implementation.
Something like this (untested).
struct FixedArray<Element> {
typealias T = Element
private (set) var count: Int = 0
private var elements: (T?, T?, T?, T?, T?, T?, T?, T?, T?, T?) = (nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
init() {
}
init(_ v0: T, _ v1: T? = nil, _ v2: T? = nil, _ v3: T? = nil, _ v4: T? = nil, _ v5: T? = nil, _ v6: T? = nil, _ v7: T? = nil, _ v8: T? = nil, _ v9: T? = nil) {
append(v0)
guard let v1 else { return }
append(v1)
guard let v2 else { return }
append(v2)
guard let v3 else { return }
append(v3)
guard let v4 else { return }
append(v4)
guard let v5 else { return }
append(v5)
guard let v6 else { return }
append(v6)
guard let v7 else { return }
append(v7)
guard let v8 else { return }
append(v8)
guard let v9 else { return }
append(v9)
}
subscript (_ index: Int) -> T {
get {
switch index {
case 0: return elements.0!
case 1: return elements.1!
// ...
default: fatalError("index out of bounds")
}
}
set {
switch index {
case 0: elements.0 = newValue
case 1: elements.1 = newValue
// ...
default: fatalError("index out of bounds")
}
}
}
mutating func append(_ element: T) {
precondition(count < 10)
self[count] = element
count += 1
}
func forEach(_ execute: (T) -> Void) {
for i in 0 ..< count {
execute(self[i])
}
}
func filter(_ execute: (T) -> Bool) -> FixedArray {
var filtered = FixedArray()
for i in 0 ..< count {
let element = self[i]
if execute(element) {
filtered.append(element)
}
}
return filtered
}
func map<R>(execute: (T) -> R) -> FixedArray<R> {
var mapped = FixedArray<R>()
for i in 0 ..< count {
mapped.append(execute(self[i]))
}
return mapped
}
func reduce<R>(_ initial: R, execute: (R, T) -> R) -> R {
var r = initial
for i in 0 ..< count {
r = execute(r, self[i])
}
return r
}
}
IIUC, when you work with type erased values you work with a (value type) box that points to a heap allocated object, whose payload is the actual value. That will cause ARC traffic... To avoid it consider using an enum:
enum BxDF {
case diffuse(Diffuse)
case dielectric(Dielectric)
case conductor(Conductor)
case hair(Hair)
func evaluate() {
switch self {
case .diffuse(let diffuse):
diffuse.evaluate()
case .dielectric(let dielectric):
dielectric.evaluate()
}
}
}
struct Diffuse {
func evaluate() {}
}
struct Dielectric {
func evaluate() {}
}
In this case you'll be working with real unboxed values (in C that would be called "union") of a size big enough to encapsulate the biggest member plus a discriminator value. That should be ARC-free.
This is a good idea. Another one worth considering is if you can distribute the single array of n polymorphic values of N datatypes into N separate arrays of monomorphic values, one per datatype.