在使用Metal的旧设备上,内存使用量不断增加。

7
我使用 MetalCADisplayLink 实时过滤一个 CIImage 并将其渲染到 MTKView 中。
// Starting display link 
displayLink = CADisplayLink(target: self, selector: #selector(applyAnimatedFilter))
displayLink.preferredFramesPerSecond = 30
displayLink.add(to: .current, forMode: .default)

@objc func applyAnimatedFilter() {
    ...
    metalView.image = filter.applyFilter(image: ciImage)
}

根据Xcode中的内存监视器,iPhone X上的内存使用稳定,从不超过100mb,在像iPhone 6或iPhone 6s这样的设备上,内存使用情况会不断增长,直到最终系统关闭应用程序。 我使用Instruments检查了内存泄漏,但没有报告任何泄漏。通过Allocations运行应用程序也没有显示任何问题,并且应用程序不会被系统关闭。我还发现有趣的是,在新设备上内存使用稳定,但在旧设备上却不断增长。滤镜的复杂度并不重要,因为即使使用最简单的滤镜,问题仍然存在。以下是我的金属文件示例:
extern "C" { namespace coreimage {

    float4 applyColorFilter(sample_t s, float red, float green, float blue) {

        float4 newPixel = s.rgba;
        newPixel[0] = newPixel[0] + red;
        newPixel[1] = newPixel[1] + green;
        newPixel[2] = newPixel[2] + blue;

        return newPixel;
    }
}

I wonder什么会导致旧设备出现问题,应该向哪个方向查找。
更新1:这里有两个1分钟的图表,一个来自Xcode,另一个来自Allocations,都使用相同的过滤器。Allocations图表是稳定的,而Xcode图表始终在增长。

Xcode

Allocations

更新2: 附上一个已按大小排序的分配列表的截图,该应用程序运行了16分钟,不停地应用过滤器:

enter image description here

更新3: 关于 applyAnimatedFilter() 中正在发生的事情,我有更多信息:

我将一个经过滤镜处理的图像渲染到一个MTKView中。我从filter.applyFilter(image: ciImage)接收到经过滤镜处理的图像,接下来在Filter类中进行处理。

 func applyFilter(image: ciImage) -> CIImage {
    ...
    var colorMix = ColorMix()
    return colorMix.use(image: ciImage, time: filterTime)
 }

其中filterTime只是一个Double变量。最后,这是完整的ColorMix类:

import UIKit

class ColorMix: CIFilter {

    private let kernel: CIKernel

    @objc dynamic var inputImage: CIImage?
    @objc dynamic var inputTime: CGFloat = 0

    override init() {

        let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
        let data = try! Data(contentsOf: url)
        kernel = try! CIKernel(functionName: "colorMix", fromMetalLibraryData: data)
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func outputImage() -> CIImage? {

        guard let inputImage = inputImage else {return nil}

        return kernel.apply(extent: inputImage.extent, roiCallback: {
            (index, rect) in
            return rect.insetBy(dx: -1, dy: -1)
        }, arguments: [inputImage, CIVector(x: inputImage.extent.width, y: inputImage.extent.height), inputTime])
    }

    func use(image: CIImage, time: Double) -> CIImage {

        var resultImage = image

        // 1. Apply filter
        let filter = ColorMix()
        filter.setValue(resultImage, forKey: "inputImage")
        filter.setValue(NSNumber(floatLiteral: time), forKey: "inputTime")

        resultImage = filter.outputImage()!

        return resultImage
    }

}

你尝试过添加自动释放池吗? - matt
@matt 我没有这样做,我认为在一个纯Swift项目中它并没有什么用处? - SmartTree
你对此有所误解。 :) 我不能保证这会解决这个特定的问题,但至少让我们尝试一下。将applyAnimatedFilter的整个内部包装在一个autoreleasepool块中,看看是否有任何区别。 - matt
@matt 谢谢,我现在就试试 :) 另外我注意到,在Instruments中运行应用程序时,它不会被杀死,内存图表也很稳定。 Xcode和Instruments之间的内存管理是否有任何差异? - SmartTree
@matt 我在更新中包含了两个图表的截图。 - SmartTree
显示剩余18条评论
2个回答

7
这是Xcode诊断功能(Metal验证和/或GPU帧捕获)中的一个错误。如果关闭这些功能,则内存使用量应该与在Xcode外运行时类似。

这个对我也有用!我有一个视频游戏引擎,我已经提前分配了所有游戏的内存,但出乎意料的是内存可能会继续增加。谢谢! - Theo Bendixson
SDL 导致了内存泄漏,令人惊讶的是它并非真正的 SDL,而是我在 Scheme 编辑器中开启了 Metal API 验证。非常感谢! - davecom

2
以下是一些观察结果,但我不确定它们中的哪一个实际上导致了您看到的内存使用情况:
- 在`applyFilter`中,您在每帧中创建一个新的`ColorMix`过滤器。此外,在实例方法`use(image:,time :)`中,您在每次调用时又创建了另一个。这是很多的开销,特别是因为该过滤器每次在`init`上加载其内核。建议在设置期间只创建一个单独的`ColorMix`过滤器,并在每个帧上仅更新其`inputImage`和`inputTime`。 - `outputImage`不是一个`func`,而是一个您从`CIFilter`超类重写的`var`: ``` override var outputImage: CIImage? { /* your code here */ } ``` - 您的`colorMix`内核是否执行任何卷积?如果没有,它可以是一个`CIColorKernel`。 - 如果您需要内核中输入的大小,则无需将其作为额外参数传递。您可以在输入`sampler`上调用`.size()`。

嘿,弗兰克,感谢你的优化技巧!我会在我的代码中加以应用。每帧创建一个新的过滤器真的很不理性。肯·托马斯的建议是,在方案中关闭Metal验证和GPU捕获,这对我很有帮助,现在Xcode中的内存图稳定了。 - SmartTree
很好!很高兴我能帮到你。 - Frank Rupprecht

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接