Debugging my bizarre macOS memory issue in Swift

I was seeing some strange behaviour in the memory consumption of an app I’m working on. Being the Swift newbie I am, here’s how I figured out what’s up. Spoilers: There was no bug and computers are mystical entities.

Act 1: The problem

So I’m currently working on an app for macOS that has to bulk-load image files the user provides into CIImage instances to make some modifications to each image.

Thing is, it appears as though Core Image does something interesting when you tell it to load a bunch of images to memory and then release them again: Even after releasing all CIImage variables, memory usage of the app stays at a ludicrous level.

After processing & releasing all image files, app memory remains at 444mb

Having bashed my head against a wall with this problem for a bit, I decided to figure out the simplest code that could trigger this issue, which lead me to this code snippet here:

@IBAction func loadImages(sender: Any?) {
    let panel = NSOpenPanel()
    panel.allowsMultipleSelection = true
    panel.allowedFileTypes = [kUTTypeImage as String]
    let result = panel.runModal()

    if (result.rawValue == NSFileHandlingPanelOKButton) {
        for url in panel.urls {
            autoreleasepool {
                var image = CIImage(contentsOf: url)
                print(image?.extent.width)
            }
        }
    }
}

So far, it seems that just loading and releasing bunch of CIImages causes the issue. So my code dealing with image manipulation is off the hook for this memory mess for now. Off to Stackoverflow I went, full of hopes & dreams of an easy fix, provided by some wizards on the internetz.

Act 2: Instruments

Fast forward about a week: Not a single useful response to my Stackoverflow question. Clearly I had come across an issue so advanced, not even wizards could help me here. Dang it.

So, having read some articles posts and guides to understand how to debug apps with interesting behaviours in terms of memory usage, let’s have a look at what Instruments’ Allocation & Leak-detection tools have to say about this.

There appeare to be no leaks from our Applications... and the memory usage of the app is much lower than I'd expect. WTF!?

So, uh… this is interesting. According to Instruments, there are no Memory Leaks that originate from the app & it’s code itself. Even more interesting: Instruments reports a memory usage of ~30MB, while Activity Monitor reports well over 400MB of used RAM. WTF?

Okay, this seems strange. But, being a newbie to a lot of the concepts at play here, maybe I’m just misunderstanding stuff. Better do some more reading on the topic and get back to it then.

Act 3: What. The. Fuck

So I got distracted took a tactical pause before throwing myself at a few more articles on how Swift deals with memory management and I noticed something that’s even stranger than the numbers from Instruments’ Leak & Allocation tools:

When I let the app sit idly in the background, the amount of consumed memory begins to shrink.

To make sure this was actually what was happening here, I started a new build of the app, did a few “runs” of loading images & let the app sit idly in the background, while watching some excellently nerdy entertainment. And sure enough, memory usage starts at well over 400MB after the runs and miraculously dips to ~80MB.

Memory usage miraculously goes down when doing nothing

What. The. Fuck!?

Contemplating whether or not my MacBook’s RAM might just have been cursed by some kind of malign Chromium-devil, I handed a build of my snippet over to a friend. You know… just to sanity check against demons & stuff eating my RAM. My friend came back with the same results as I did: The app’s memory footprint decreases over time when actively using the machine.

Act 4: My knowledge of memory is bad & I should feel bad.

After far too much time spend googling for a specific Core Image issue, I now searched for a more general, “Why do Instruments and Activity Monitor disagree so much” issue. And sure enough: The wizards of Stackoverflow did have an answer. From 2011.

Activity Monitor is useless for development/debugging purposes.

[…]

Note that a system not under memory pressure will often not request that applications give back memory. That is, you may not see a drop in Activity Monitors numbers.

Ah.
So my app was, in fact, not consuming egregious amounts of memory. Activity Monitor was just displaying a different measurement from the system that makes it seem like there’s an outrageous amount of RAM being taken up when really macOS is just not asking for that RAM back.
That’s… good to know.

Act 5: Conclusion

So, what did we learn this week?

  • My app is actually fine. Memory usage isn’t nearly as much of an issue as I had feard
  • Don’t use Activity Monitor / Xcode as a measure of performance / RAM usage. There’s a tool for that. It’s called Instruments.
  • The RAM reported by Activity Monitor may not necessarily reflect the actual state of the apps running on my system. (Although, toggling on “Real Mem” to be part of Activity Monitor’s Table View sure can’t hurt.)
  • The people who work on this kinda low-level code are far too smart for me. Like, really way too smart. I’m talking “Getting a USB A connector attached on the first try”-levels of smart here.

I hope that maybe, down the line, this post might help someone with a similar problem to figure things out much quicker than I was able to. Or that it at least provided some entertainment to those of you who’ve been shaking their heads the entire way through this article because of how obvious my issue was.

If you’ll excuse me, I’ll be picking up some more Ibuprofen from the pharmacy. Banging my head against Xcode for a week really does cause some interesting kinds of headaches, let me tell you.

PS: While working on this “issue” I had found a blog post that explains macOS’ way of allocating, freeing & providing RAM in advance in much greater detail. Sadly, I have not been able to find this post again. If someone here knows what that post might have been, eMail me so I can include a link here.