SceneKit - 由 childNode 查找导致的无限内存增长
SceneKit - Unbounded Memory Growth cause by childNode lookup
我有一个应用程序在循环中对 SCNNode
s 执行操作 - 应用程序的内存达到 2GB 并崩溃。我的实际循环非常复杂——它包括更新节点的 simdWorldTransform
,以及删除和添加节点。
为了简化到我可以问这个问题的程度,我做了一个简单的例子,按下一个按钮,它运行非常简单的动作,比如检索 SCNNode 的子节点——它仍然达到 2GB 并崩溃.
函数:
func memoryLeakDetect() {
for i in 1..<2001 {
if i % 10 == 0 {
print("Iteration = \(i)")
}
simpleLoop()
}
}
simpleLoop()
:
func simpleLoop() {
for i in 0..<10000 {
let pickIndex = 1
let pickLabelComp = saLabelComponents[pickIndex]
let pickSprite = LabelCompUtils.extractSprite(from: pickLabelComp) //MARK: CAUSING MEMORY LEAK!!!!
// let pickSprite = saSprites[pickIndex]
}
}
似乎罪魁祸首是对 extractSprite
的调用 - 因为如果我通过将对 sprite 的引用存储在数组中来删除它,程序将不再崩溃。我包括 extractSprite
方法。我完全傻眼了,为什么这会导致无限的内存增长。是什么导致了这种内存爆炸?我只是在查找一个节点?退出迭代后不应该在幕后保留任何引用?
class LabelCompUtils {
static func extractSprite(from labelComponent: SCNNode) -> SCNNode {
if let sprite: SCNNode = labelComponent.childNode(withName: "sprite", recursively: false) {
return sprite
} else {
return SCNNode()
}
}
对于正在处理这种情况的任何其他人(似乎有很多人),以下步骤解决了该问题。
autoreleasepool
: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
正如@ElTomato 所建议的,由于循环可能会在循环中创建临时对象,因此建议使用 autoreleasepool
。根据 Apple 的文档:"You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application."
示例:
// add leaderLineNodes
for leaderLine in createleaderLineNodes(labelComponent: labelComponent) {
autoreleasepool {
labelComponent.addChildNode(leaderLine)
}
}
- 运行 您正在 "release mode".
中开发的应用程序
Xcode 中的性能可能与实际设备性能不同。第 2 点和第 3 点在内存占用和执行速度方面严重影响了我的应用程序。最初在 Instruments 中分析应用程序时我感到非常惊讶,发现使用的内存比以前低得多 - 结果 Instruments 在发布模式下运行应用程序。
Product -> Scheme -> Edit Scheme -> Info -> Build Configuration -> Release Mode
- 禁用日志记录
Product -> Scheme -> Edit Scheme -> Diagnostics -> Logging
我有一个应用程序在循环中对 SCNNode
s 执行操作 - 应用程序的内存达到 2GB 并崩溃。我的实际循环非常复杂——它包括更新节点的 simdWorldTransform
,以及删除和添加节点。
为了简化到我可以问这个问题的程度,我做了一个简单的例子,按下一个按钮,它运行非常简单的动作,比如检索 SCNNode 的子节点——它仍然达到 2GB 并崩溃.
函数:
func memoryLeakDetect() {
for i in 1..<2001 {
if i % 10 == 0 {
print("Iteration = \(i)")
}
simpleLoop()
}
}
simpleLoop()
:
func simpleLoop() {
for i in 0..<10000 {
let pickIndex = 1
let pickLabelComp = saLabelComponents[pickIndex]
let pickSprite = LabelCompUtils.extractSprite(from: pickLabelComp) //MARK: CAUSING MEMORY LEAK!!!!
// let pickSprite = saSprites[pickIndex]
}
}
似乎罪魁祸首是对 extractSprite
的调用 - 因为如果我通过将对 sprite 的引用存储在数组中来删除它,程序将不再崩溃。我包括 extractSprite
方法。我完全傻眼了,为什么这会导致无限的内存增长。是什么导致了这种内存爆炸?我只是在查找一个节点?退出迭代后不应该在幕后保留任何引用?
class LabelCompUtils {
static func extractSprite(from labelComponent: SCNNode) -> SCNNode {
if let sprite: SCNNode = labelComponent.childNode(withName: "sprite", recursively: false) {
return sprite
} else {
return SCNNode()
}
}
对于正在处理这种情况的任何其他人(似乎有很多人),以下步骤解决了该问题。
autoreleasepool
: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
正如@ElTomato 所建议的,由于循环可能会在循环中创建临时对象,因此建议使用 autoreleasepool
。根据 Apple 的文档:"You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application."
示例:
// add leaderLineNodes
for leaderLine in createleaderLineNodes(labelComponent: labelComponent) {
autoreleasepool {
labelComponent.addChildNode(leaderLine)
}
}
- 运行 您正在 "release mode". 中开发的应用程序
Xcode 中的性能可能与实际设备性能不同。第 2 点和第 3 点在内存占用和执行速度方面严重影响了我的应用程序。最初在 Instruments 中分析应用程序时我感到非常惊讶,发现使用的内存比以前低得多 - 结果 Instruments 在发布模式下运行应用程序。
Product -> Scheme -> Edit Scheme -> Info -> Build Configuration -> Release Mode
- 禁用日志记录
Product -> Scheme -> Edit Scheme -> Diagnostics -> Logging