Anyone know why `Exception#backtrace` uses so much memory?

@rogerdpack Nice find, this seems to be exactly what it is. The load_debug_info method is called from several methods invoked inside Exception::CallStack#decode_backtrace to memoize the debug info, so setting CRYSTAL_LOAD_DEBUG_INFO=1 just changes when that method is called and the data loaded. I set CRYSTAL_LOAD_DEBUG_INFO=1 and as soon as the process started it was using ~50MB of RAM, and getting an exception made no meaningful difference.

Unfortunately, this means that there is a choice between using an order of magnitude more memory for small services vs making exceptions slower any time the app reads backtraces as it reloads the data from disk every time. Classic space-vs-time tradeoff.

On the bright side, the monkeypatch to release this memory (on Linux/macOS/BSD, at least) probably looks something like this:

struct Exception::CallStack
  private def decode_backtrace
    previous_def.tap do
      @@dwarf_loaded = false
      @@dwarf_line_numbers = nil
      @@dwarf_function_names = nil
    end
  end
end

I’ll give it a try in the morning.

2 Likes