Быстрая слабая ссылка намного медленнее, чем сильная ссылка - PullRequest
2 голосов
/ 31 октября 2019

Я строю физический движок в Свифте. После внесения некоторых недавних дополнений в двигатель и проведения тестов производительности я заметил, что производительность была значительно ниже. Например, на скриншотах ниже вы можете увидеть, как FPS упал с 60 до 3 FPS (FPS находится в правом нижнем углу). В итоге я отследил проблему до одной строчки кода:

final class Shape {
    ...
    weak var body: Body! // This guy
    ...
}

В какой-то момент в моих добавлениях я добавил слабую ссылку из класса Shape в класс Body. Это сделано для предотвращения сильного ссылочного цикла, так как Body также имеет сильную ссылку на Shape.

К сожалению, кажется, что слабые ссылки имеют значительные накладные расходы (я предполагаю, что дополнительные шаги в его обнулении),Я решил исследовать это дальше, построив значительно упрощенную версию физического движка, приведенного ниже, и сравнив различные эталонные типы.


import Foundation

final class Body {
    let shape: Shape
    var position = CGPoint()
    init(shape: Shape) {
        self.shape = shape
        shape.body = self

    }
}

final class Shape {
    weak var body: Body! //****** This line is the problem ******
    var vertices: [CGPoint] = []
    init() {
        for _ in 0 ..< 8 {
            self.vertices.append( CGPoint(x:CGFloat.random(in: -10...10), y:CGFloat.random(in: -10...10) ))
        }
    }
}

var bodies: [Body] = []
for _ in 0 ..< 1000 {
    bodies.append(Body(shape: Shape()))
}

var pairs: [(Shape,Shape)] = []
for i in 0 ..< bodies.count {
    let a = bodies[i]
    for j in i + 1 ..< bodies.count {
        let b = bodies[j]
        pairs.append((a.shape,b.shape))
    }
}

/*
 Benchmarking some random computation performed on the pairs.
 Normally this would be collision detection, impulse resolution, etc.
 */
let startTime = CFAbsoluteTimeGetCurrent()
for (a,b) in pairs {
    var t: CGFloat = 0
    for v in a.vertices {
        t += v.x*v.x + v.y*v.y
    }
    for v in b.vertices {
        t += v.x*v.x + v.y*v.y
    }
    a.body.position.x += t
    a.body.position.y += t
    b.body.position.x -= t
    b.body.position.y -= t
}
let time = CFAbsoluteTimeGetCurrent() - startTime

print(time)

Результаты

Ниже приведены результаты тестовдля каждого ссылочного типа. В каждом тесте ссылка body на класс Shape изменялась. Код был построен с использованием режима релиза [-O] с Swift 5 для MacOS 10.15.

weak var body: Body!: 0,1886 с

var body: Body!: 0,0167 с

unowned body: Body!: 0,0942 с

Вы можете видеть, что использование сильного эталона в приведенных выше вычислениях вместо слабого эталона приводит к увеличению производительности в 10 раз. Использование unowned помогает, но, к сожалению, оно все еще в 5 раз медленнее. При выполнении кода через профилировщик, как представляется, выполняются дополнительные проверки во время выполнения, что приводит к значительным накладным расходам.

Так что вопрос , каковы мои варианты для получения простого обратного указателя наКузов без навеса над этим ARC. И кроме того, почему эти накладные расходы кажутся такими экстремальными? Я полагаю, я мог бы сохранить сильный контрольный цикл и разорвать его вручную. Но мне интересно, есть ли лучшая альтернатива?

1 Ответ

3 голосов
/ 31 октября 2019

Когда я писал / исследовал эту проблему, я в конце концов нашел решение. Чтобы иметь простой обратный указатель без накладных проверок weak или unowned, вы можете объявить тело как:

unowned(unsafe) var body: Body!

Согласно документации Swift:

Swift такжепредоставляет небезопасные ссылки без ссылок для случаев, когда необходимо отключить проверки безопасности во время выполнения, например, по соображениям производительности. Как и во всех небезопасных операциях, вы берете на себя ответственность за проверку этого кода на безопасность.

Вы указываете небезопасную ссылку без ссылки, написав неподтвержденную (небезопасную). Если вы попытаетесь получить доступ к небезопасной неопознанной ссылке после того, как экземпляр, на который она ссылается, будет освобожден, ваша программа попытается получить доступ к той области памяти, где находился экземпляр, что является небезопасной операцией

ясно, что эти проверки во время выполнения могут привести к серьезным накладным расходам в критичном для производительности коде.

...