Does breakpoint logging have to be 350x slower than print()?

(This is related to everyday use of Swift, but maybe it's the wrong forum and I should post somewhere that is more focused on LLDB.)

I recently thought that I need to learn to use the debugger better and not rely so much on print(...) or os.Logger statements. But the following code demonstrates a problem with this idea. Breakpoints are so slow they really affect the running program. I'm working on stuff with a lot of loops, processing polygon vertexes, stuff like that; so it's an issue.

I'm running this in Xcode, on a MacBook Pro. I have not used a Linux or Windows debugger in a long time. Maybe it's the same everywhere?

var array = [Int](repeating: 0, count: 10_000)

func doItWithPrint() {
    let timeStart = DispatchTime.now()
    for i in array.indices {
        if i % 100 == 0 {
            print("i = \(i) (print)")
            array[i] = i
        }
    }
    let timeEnd = DispatchTime.now()
    let timeDur = Double(timeEnd.uptimeNanoseconds - timeStart.uptimeNanoseconds) / 1e6
    print("Total time (print): \(timeDur) milliseconds")
}

func doItWithLLDB() {
    let timeStart = DispatchTime.now()
    for i in array.indices {
        if i % 100 == 0 {
            // Set breakpoint here, with action: Log Message, 
            // and message: "i = @i@ (breakpoint)"
            // and check "Automatically continue after evaluating actions"
            array[i] = i  
        }
    }
    let timeEnd = DispatchTime.now()
    let timeDur = Double(timeEnd.uptimeNanoseconds - timeStart.uptimeNanoseconds) / 1e6
    print("Total time (breakpoint): \(timeDur) milliseconds")
}

doItWithPrint()
doItWithLLDB()

The output:

i = 0 (print)
[...]
Total time (print): 13.562691 milliseconds
[...]
Total time (breakpoint): 4629.717516 milliseconds
1 Like

Yielding to the debugger isn’t a cheap operation. It involves the app sending an EXC_BREAKPOINT signal to itself, which the debugger intercepts. That’s going to be orders of magnitude slower than a simple print() expression.

It would be great if debug builds left enough space in the function preamble to insert some detouring code. Then when you add a breakpoint, the debugger could detour the entire function to a copy that calls print() inline. But that would require dynamically recompiling (or at least re-assembling) the code, which is no small feat.