Debugging extremely slow

Why are you using a debugger in the first place? If you write small functions, test them using unit test and use them to assemble larger functions you will never need a debugger…

Not this again. You need debuggers to understand how code works and what it is doing.

19 Likes

Well, if I want to understand a specific piece of code I copy it line by line in the REPL and inspect the intermediate results…

The talk in the way “don’t use the debugger because it’s not properly working” doesn’t make sense.
In the first place, lack of a fast, robust debugging tool prevents professional researchers/developers around me from moving from matlab to Julia. Development on Julia is simply more expensive.

12 Likes

How is this better? And how do you step-in and step-out of functions? How do you handle loops? You can’t copy half a loop without getting syntax errors. How do you handle scoping issues?

How is this even remotely as good as a debugger?

13 Likes

From the standpoint of JuliaInterpreter performance, at present the only real solution is to push! more modules or methods onto JuliaInterpreter.compiled_modules or JuliaInterpreter.compiled_methods, respectively. While I demoed that above for Base modules (Debugging in Juno extremely slow - #4 by tim.holy), this doesn’t have to be limited to Base, you can also push package modules, too.

The other alternative is to use something besides the debugger, like @show statements with Revise, Infiltrator, etc.

4 Likes

FWIW, there are some utilities for mutating compiled_modules/compiled_methods in VS Code, so you might want to give the debugger there a go.
We’re running all Base methods in compiled mode (except for a sadly incomplete list of higher order functions that need to be interpreted) and there’s a special ALL_MODULES_EXCEPT_MAIN token that runs every method not defined in Main in compiled mode.

2 Likes

Thanks a lot. After putting ALL_MODULES_EXCEPT_MAIN into settings.json the debugger takes about 23 sec for reaching a first line of own code. It’s better than ~30 sec.

If you’ve already got a REPL in VS code open, with most things compiled and the main packages on the compiled modules list, then using @enter to step into a function usually only takes a couple of seconds and is usually much faster than debugging in a new process. For example:

using Revise
includet("main.jl") # defines a `main` function
@enter main()

This also helps if loading/creating objects is costly but not what you want to debug as you generate once and pass into the function and modify the code and continue to debug until it works.

11 Likes

Anyway, 23 sec to start a debugger is very slow obviously.
I have tested some script without Makie (without charting at all) in the same env ALL_MODULES_EXCEPT_MAIN and got ~13 sec. It is not fast either.

Are such dalays normal? Or may be something wrong with my environment and could be adjusted.

I’d recommend following @jmair’s suggestion, which should cut down on the overhead by a lot.

@jmair’s suggestion really helps. Debugger starts on second and next tries immediately. Breakpoints work.
Thanks a lot.

5 Likes

Could you show a beginner how does one push package modules?

How would it work if the function foo() is already defined in a package MyPackage I created? Do I have to includet a copy of it saved to a file? Can I just paste the function definition in the REPL? Would I have to do @enter MyPackage.foo()?

In my experience the slow speed of debugging is a problem for Julia. I could not make it faster. There are no detailed examples anywhere. The people who suggest solutions are very knowledgeable and do not seem to understand the predicament of a beginner. Step by step instructions on how to make debugging faster is what is needed.

I created my own solution, which works except that I cannot go up and down in the stack. Just before a function is called I serialize the arguments that will be passed to it, then stop the program with error, then go to the REPL, deserialize the variables and manually run the code in the function’s body. That works, and is better than Infiltrator because: 1) I can do literally anything, even rewrite the whole function body, 2) I can run the program in the VSCode REPL, which cannot be done in Infiltrator.

I am not even a programmer. People more knowledgeable should be able to improve on what is below.

These are instructions:

# If the function foo(modeldata, df_xr, data, parameter, ctrl_parms)
# is called with arguments (modeldata, df_xray, precalc_data, parameter, ctrl_parms)

# insert the following just before the function call:

d_symbs = Dict(
	:modeldata => modeldata,
	:df_xr => df_xray,
	:data => precalc_data,
	:parameter => parameter,
	:ctrl_parms => ctrl_parms
)

# You can use another path if you wish
debug_path = "C:\\Users\\fsald\\Dropbox\\Temp\\dsymbs.srlz"

serialize(debug_path, d_symbs)

error("\n*&*&*&*&*&*&*&*&*&*&*& STOP HERE\n") 

# Then go to the REPL and call

deserialize_symbs(debug_path)

# After that you can run the code in foo, alter it as you wish etc.

The function deserialize_symbs is below. It must be run in the top level. Otherwise one would have to put the module name and a dot before each variable name. For example, it it was defined in module M one would have to write M.modeldata instead of just modeldata.

#@deserialize_symbs
"""
    deserialize_symbs(
        path = "C:\\Users\\fsald\\Dropbox\\Temp\\Symbs.srlz"
    ):Nothing

Deserializes a dictionary whose keys are symbols and whose names are any
values. Then assigns the values to the names corresponding to the symbols.

# Arguments
- `path`: the path where the dictionary has been serialized.

# Example
julia> debug_path = "C:\\Users\\fsald\\Dropbox\\Temp\\debug.srlz"
d = [:x => 1, :y => "W"]
serialize(debug_path, d)
x = 999 # changed the value of x
deserialize_symbs(debug_path)
x == 1 # true, x now has the value that was deserialized

Last Edited: 2023-05-31
"""
function deserialize_symbs(path::String):Nothing
    d_symbs = deserialize(path)
    for (symb, v) in d_symbs
        @eval(($symb) = ($v))
    end
end # deserialize_symbs

If you are working with a package:

julia> using MyPackage # exports myfunc()
julia> using Revise
julia> Revise.track(MyPackage)
julia> @enter myfunc()
julia> @enter MyPackage.myfunc() # alternative

This usually works for me, but not 100% perfect. Debugging in Julia isn’t as nice a experience as in other languages (e.g. C# in VS), but this is a tradeoff with having a very good REPL based workflow instead.

P.s. Try to make sure you add the heavy packages to the compiled modules (can be done through VS code Julia panel - usually on the bottom left - e.g. CUDA.*) to make sure debugging is fast enough to be useable.

push!(JuliaInterpreter.compiled_modules, Base). Do this before you start debugging. You can add packages to this, too: push!(JuliaInterpreter.compiled_modules, SomePackage). If you want to be more comprehensive and include inner modules, you can do this:

using JuliaInterpeter, MethodAnalysis
union!(JuliaInterpreter.compiled_modules, child_modules(Base))

This adds an additional 44 modules that are inside Base, so that all of Julia’s own code will be run in compiled mode. This should give you an experience that’s a little closer to what you’d get in Matlab or Python, where the internal code is mostly in C and can’t be interpreted.

5 Likes

Thanks. Very helpful.

Just to clarify, adding the child modules of Base seems to add more code to the list that is run in compiled mode. So should I do the following? Or is the second line not needed given that the third line will be executed?

using JuliaInterpreter, MehodAnalysis
push!(JuliaInterpreter.compiled_modules, Base)
union!(JuliaInterpreter.compiled_modules, child_modules(Base))
push!(JuliaInterpreter.compiled_modules, MyPackage)

How does this compare with clicking on the + sign in the Run and Debug area of VSCode and adding MyPackage?

Should I load Revise.jl while I am debugging with the Julia Interpreter? What would be the pros and cons?

I am assuming

...
@enter myfunc() # or @enter MyPackage.myfunc()
...

would replace the call

...
myfunc()
...

within my code. Is this correct?

I just run the @enter line in the REPL directly, without putting it in the code.