JuliaDebug / Gallium.jl

The Julia debugger
Other
174 stars 23 forks source link

breakpoints #197

Open zsunberg opened 6 years ago

zsunberg commented 6 years ago

Hi all, in a meeting a few weeks ago, @ViralBShah asked us for some feedback on the debugger. Here is my take on it:

Having a working @enter as we do now is great! Thanks for implementing that! I agree with @rejuvyesh that keeping that working should be priority number 1. But unfortunately @show and @assert are still my most effective tools for debugging (and this is definitely suboptimal). The reason is that I have never been able to conveniently set breakpoints in Gallium.

I think I am in the same camp as the people in the discourse debug discussion who are scientists first, programmers second. Here is what typically happens: I write a big, complex simulation that involves random numbers. 2 minutes into the simulation, it has an error halfway through a 50 line function because some value is outside of its feasible range. My first thought is "how do I get there". I could do it with @enter, but then I have to do a prohibitive amount of thinking about how to record the inputs for the function (basically building a test fixture) which might include a random number generator state, etc.

The best debugging experience I've had in Julia was the old Debug.jl package, and I have heard others say this as well. In Debug.jl, interaction with the code was much worse, but setting breakpoints where I need them is much more important. Some (@mykelk) will make the (valid) argument that having to change code is bad, but I think this is a secondary issue, especially in light of the usefulness of pdb to scientists that program in python.

Perhaps breaking at points other than a function call is difficult in the Gallium framework, and I certainly don't mean to disparage your efforts - they have been impressive, but, in my opinion, to make the package really useful, there needs to be a way to insert conditional breakpoints into the code.

Thanks for considering this!

If this isn't appropriate for an issue, feel free to close and I can move it to the appropriate place (maybe discourse).

timholy commented 6 years ago

Worthy points. I'm sure you're also aware of how much harder it is to build a debugger for a language like Julia than it is for an interpreted language.

Until Gallium matures, what follows may be obvious, but in case not perhaps it's worth recording my current debugging strategy (which I don't dispute will become nicer once we have a full-featured debugger). Let's say I've run a program that crashes in badfunc which looks something like this:

function badfunc(args...)
    # stuff
    value = someprivatefunction(y, ...)
    # stuff
    x = sqrt(value)   # here's where it crashes, because `value` is sometimes negative
    # stuff
end

Because I use Revise, in the same julia session, I modify this to be something like

const debug = []
function badfunc(args...)
    # stuff
    value = someprivatefunction(y, ...)
    # stuff
    if value < 0
        empty!(debug)
        push!(debug, (args...,))
        push!(debug, y)
        # any other intermediates
        push!(debug, value)
    end
    x = sqrt(value)   # here's where it crashes, because `value` is sometimes negative
    # stuff
end

and save the file, then run my code again. After the crash, I extract values

julia> args, y, [more variables], value = MyPkg.debug

(using the same variable names as appear in my code) and inspect them. If necessary, I might run intermediate steps by copy/pasting into the REPL prompt and adding necessary $ signs in front of arguments

julia> newvalue = @eval MyPkg someprivatefunction($y, ...)

so that I can inspect values that I failed to capture in debug. In totality, this effectively acts like a conditional breakpoint or breakpoint-on-error (yes, with more manual labor on my part) without all the @show statements or significant performance hit when the function doesn't throw the error.

With Revise, these code modifications are quick and easy. And once you've solved the problem, you delete these additions and get back to your work, again without having to restart your Julia session. I've never timed it, but in simple cases I think I sometimes complete this whole cycle in under 30 seconds when formerly it usually took several minutes just to recompile all the packages triggered by adding the necessary Debug.jl statements.

With strategies like these, overall I find debugging much faster now than back in the days when Debug.jl worked but you had to restart your Julia session for each new function you wanted to debug. So I think the situation is getting considerably better, not worse, but will only be glorious once Gallium and associated packages have had time to come to fruition.

Of course, these are not orthogonal to Debug.jl, and if you like it there's no reason you can't use Revise + Debug if you or others are willing to submit fixes to that project until it works on 0.6 or 0.7. Debug is "typical" julia code, and doesn't require any of the magic that Gallium & associated packages are implementing to build a debugger that can go "backwards" from the native (compiled) code. I haven't looked myself, but it might be relatively straightforward to fix it---if memory serves it is well-organized and commented. (As a tip, it might be best to start with the last known version of julia that Debug.jl worked with and then "follow the deprecation warnings" forward.)

Anyway, I hope this helps hold you over until the full blossoming of Gallium.

zsunberg commented 6 years ago

@timholy Thanks for the example - that is very useful for me and anyone else who happens upon this, and Revise is totally awesome.

I definitely realize that building a debugger for Julia is an incredibly difficult task. Gallium is an amazing piece of work! I just want to realistically describe the experience for a scientific user, so that we can better share Julia with the world and scientists can use it to expand knowledge!