0

I made a seemingly innocuous change to my iOS app, and the app is crashing because memory consumption constantly keeps increasing as the app runs, until it crashes. This is perhaps a 10x increase in memory consumption.

How do I find out which class or struct is responsible for this? I'm looking for something like:

CVPixelBuffer: 800MB CMSampleBuffer: 100MB CIImage: 50MB

I looked in both Xcode's memory debugger and Instruments, but found nothing.

Kartick Vaddadi
  • 4,818
  • 6
  • 39
  • 55
  • Have you tried to find any memory leak in the app ? – Mandeep Kumar Dec 26 '17 at 07:19
  • 1
    Perhaps you introduced a memory consuming type as a local variable, likely candidates would be structs and insanely large C-arrays which theoretically could deplete your stack space. More likely it's a tight loop which keeps putting something on the heap. Don't you have a revision history to narrow down the change that is causing the problem? – Kamil.S Dec 26 '17 at 08:09
  • @Kamil.S I know which commit, and it's not even committed to Git yet. It's not about stack space, since I don't have deep recursion. By painstakingly commenting out the new code and uncommenting it line by line, I found that the problem is in https://developer.apple.com/documentation/coreimage/cifilter/2138288-init If I create a CIFilter and throw it away, it leaks memory, which shouldn't happen. But does Xcode or Instruments give us a better way, namely sorting all classes by memory used (cumulatively by all instances of that class)? – Kartick Vaddadi Dec 27 '17 at 04:21
  • Interesting, I'd explore a minimal `obj-c` poc using the `CIFilter` to narrow down if the problem is in the `CIFilter` itself or it's `Swift` wrapper. In the `obj-c` space you could play around with overriding `alloc` & `dealloc` to do some tracking. – Kamil.S Dec 27 '17 at 09:15
  • Thanks, my answer gives more information. We can rule out a bug in the Objective-C-Swift interop, since such a bug would affect all classes, not just CIFilter. – Kartick Vaddadi Dec 27 '17 at 10:15

1 Answers1

0

It's a memory leak in the CIFilter initialiser. Here are the steps to reproduce:

  1. Use AVFoundation to capture a raw sample buffer.
  2. Call CMSampleBufferGetImageBuffer() to get a CVPixelBuffer:
  3. Create a CIFilter:
  4. Notice that the CIFilter is thrown away.
  5. After all steps above complete, go back to step 1, and repeat 100 times.

Here's the code:

let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
let metadata = CMCopyDictionaryOfAttachments(nil, sampleBuffer, kCMAttachmentMode_ShouldPropagate)!
_ = CIFilter(cvPixelBuffer: pixelBuffer, properties: metadata)

Expected Results:

Creating a Swift object and throwing it away won't leak memory.

Actual Results:

  • Memory usage grows in an unbounded manner to more than a gigabyte, until iOS kills the app.
  • Commenting out the line that creates the CIFilter eliminates this problem.
  • Converting the RAW sample buffer to NSData using dngPhotoDataRepresentation() and passing that to the CIFilter initialiser eliminates this problem, but seems slower.
  • The CVPixelBuffer doesn't seem to be released, because CIFilter (or some other code internal to iOS is leaking is). I verified by creating a weak reference to CVPixelBuffer. If the buffer is released, the weak reference should become nil, but it doesn't.
  • Capturing a BGRA (instead of RAW) sample buffer and converting it to a CIImage doesn't leak memory:

    _ = CIImage(cvImageBuffer: pixelBuffer, options: [kCIImageProperties: metadata])

This is on iOS 11 on iPhone 7 Plus and iPhone X.

Kartick Vaddadi
  • 4,818
  • 6
  • 39
  • 55