2

I am trying to get histogram calculation. Everything works fine, except following method shows an immense memory leak when profiled in Instruments.

Every time following method is called, it uses 200-300 MB of memory and never releases:

       func histogramCalculation(_ imageRef: CGImage) -> (red: [UInt], green: [UInt], blue: [UInt]) {

            var inBuffer = vImage_Buffer() 

            vImageBuffer_InitWithCGImage(
                &inBuffer,
                &format,
                nil,
                imageRef,
                UInt32(kvImageNoFlags))

            let alpha = [UInt](repeating: 0, count: 256)
            let red = [UInt](repeating: 0, count: 256)
            let green = [UInt](repeating: 0, count: 256)
            let blue = [UInt](repeating: 0, count: 256)

            let alphaPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: alpha) as UnsafeMutablePointer<vImagePixelCount>?
            let redPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: red) as UnsafeMutablePointer<vImagePixelCount>?
            let greenPtr = UnsafeMutablePointer<vImagePixelCount>(mutating: green) as UnsafeMutablePointer<vImagePixelCount>?
            let bluePtr = UnsafeMutablePointer<vImagePixelCount>(mutating: blue) as UnsafeMutablePointer<vImagePixelCount>?

            let rgba = [redPtr, greenPtr, bluePtr, alphaPtr]

            let histogram = UnsafeMutablePointer<UnsafeMutablePointer<vImagePixelCount>?>(mutating: rgba)
            let error : vImage_Error = vImageHistogramCalculation_ARGB8888(&inBuffer, histogram, UInt32(kvImageNoFlags))

            if (error == kvImageNoError) {
                return (red, green, blue)
            }
            return (red, green, blue)
}

What could be wrong here.....

Gizmodo
  • 3,151
  • 7
  • 45
  • 92

1 Answers1

3

The docs for vImageBuffer_InitWithCGImage explain:

You are responsible for returning the memory referenced by buf->data to the system using free() when you are done with it.

So I would expect something along these lines to clean up the memory:

inBuffer.data.deallocate(bytes: inBuffer.rowBytes * Int(inBuffer.height),
                         alignedTo: MemoryLayout<vImage_Buffer>.alignment)

As a side note, your use of UnsafeMutablePointer here is not safe. There's no promise, for instance, that alpha will exist by the time you use reference it. Swift is allowed to destroy alpha immediately after you create alphaPtr (because it's never referenced again). It is rare that you want to use UnsafeMutablePointer.init. Instead, you want to use withUnsafe... methods to establish guaranteed lifetimes. For example (untested, but compiles):

var alpha = [vImagePixelCount](repeating: 0, count: 256)
var red = [vImagePixelCount](repeating: 0, count: 256)
var green = [vImagePixelCount](repeating: 0, count: 256)
var blue = [vImagePixelCount](repeating: 0, count: 256)

let error = alpha.withUnsafeMutableBufferPointer { alphaPtr -> vImage_Error in
    return red.withUnsafeMutableBufferPointer { redPtr in
        return green.withUnsafeMutableBufferPointer { greenPtr in
            return blue.withUnsafeMutableBufferPointer { bluePtr in
                var rgba = [redPtr.baseAddress, greenPtr.baseAddress, bluePtr.baseAddress, alphaPtr.baseAddress]
                return rgba.withUnsafeMutableBufferPointer { buffer in
                    return vImageHistogramCalculation_ARGB8888(&inBuffer, buffer.baseAddress!, UInt32(kvImageNoFlags))
                }
            }
        }
    }
}
Rob Napier
  • 286,113
  • 34
  • 456
  • 610