How to improve REPL startup time OR clear the workspace

I love Julia – it’s amazing with the VS Code extension, which solves the problem of not being able to perform the simple task of plotting more than one thing at once. There’s just one thing keeping me from recommending it to anyone: startup/TTFX performance.

It wouldn’t be a problem if I could just start a REPL and include("file.jl") each time I update my code, but I frequently run into problems with previously defined identifiers overstaying their welcome, including functions that don’t reflect my changes and proceed to cause subtle bugs until I remember to restart my REPL. With the frequency at which I encounter these sorts of bugs, I can’t recommend Julia to someone that wants a deterministic programming language.

The last thing I need when I’m attacking a particularly difficult research task or programming problem is to cripple my mind with the massive amount of mental overhead involved with worrying about whether or not the logically and syntactically valid change I just made will introduce a bug into my program.

From what I’ve read in other topics, we’re extremely unlikely to get a workspace() replacement, so does anyone have any tips for reducing my startup time? I wrote some macros that output results in a format that is more convenient to me, but I think they’re the reason my startup takes so long.

Thanks for reading, and I look forward to learning more about this incredible language.

My startup.jl contains the following:

using Revise;
using LinearAlgebra;
using Plots;

# Compact show
macro showc(exs...)
    blk = Expr(:block)
    for ex in exs
        push!(blk.args, :(println($(sprint(Base.show_unquoted,ex)*" = "),
        repr(begin local value = $(esc(ex)) end, context = :compact=>true))))
    end
    isempty(exs) || push!(blk.args, :value)
    return blk
end

# Pretty show
macro showp(exs...)
    blk = Expr(:block)
    for ex in exs
        push!(blk.args, :(println($(sprint(Base.show_unquoted,ex)*" = "),
        repr(MIME("text/plain"), begin local value = $(esc(ex)) end, context = :limit=>true))))
    end
    isempty(exs) || push!(blk.args, :value)
    return blk
end

# Relatively robust floating-point comparisons
function nearly_equal(
    a::T, b::T, rel_tol::T = 128 * eps(T), abs_tol::T = eps(T)
)::Bool where T <: AbstractFloat
    # Some handy constants for determining whether the defaults are suitable:
    # eps(Float64) = 2.220446049250313e-16
    # eps(Float32) = 1.1920929f-7
    # eps(Float16) = 0.000977
    # floatmin(Float64) = 2.2250738585072014e-308
    # floatmin(Float32) = 1.1754944f-38
    # floatmin(Float16) = 6.104e-5
    # See also: nextfloat e.g. nextfloat(1000.0::Float64) - 1000.0::Float64 =
    #                              1.1368683772161603e-13
    if a == b return true; end

    diff = abs(a - b);
    norm = min(abs(a) + abs(b), floatmax(T))
    return diff < max(abs_tol, rel_tol * norm);
end

# Now make it work on vectors
function nearly_equal(
    a::Vector{T}, b::Vector{T}, rel_tol::T = 128 * eps(T),
    abs_tol::T = 128 * floatmin(T)
)::Bool where T <: AbstractFloat
    if size(a) != size(b) throw(DimensionMismatch("Dimension mismatch.")); end

    for i in 1:size(a, 1)
        if !nearly_equal(a[i], b[i], rel_tol, abs_tol) return false; end
    end

    return true;
end

If I understand your issue you want the text in the VSCode editor window to be as consistent as possible with the code that is running in the VSCode REPL.

I set VSCode to save files on focus change. When your mouse moves out of the VSCode text editor window to the REPL the file is saved, Revise detects the change and updates the definitions in the REPL This generally happens before you can begin typing in the REPL.

It’s the best way I’ve found to keep the source text consistent with the REPL.

So just like @brianguenter I reccomend using Revise.jl as it will keep source and REPL state in sync. However there are some limitations such as struct/const redefinitions that Revise can’t handle. If you find yourself changing struct definitions often, there are some work-arounds (read here for more info on background and work-arounds). Or you can use Pluto.jl for prototyping and interactivity. I personally do all my exploration/research/drafting packages in Pluto.jl and then switch over to a Revise-based workflow when creating a package.

1 Like

Julia is definitely very fit for deterministic workflows.
The way to go is to use revise (ideally in combination with a module). All your changes made in the module/revised file will be up to date.
If you don’t pollute the global namespace by defining everything at top scope then you can keep a session open for very long times.

So yeah if you want something deterministic and reproducible, a package and revise is always the best practice and imo beats workflows in which you run scripts from the shell repeatedly by far, because you can still work interactively

1 Like

Thinking about it, I wonder if one can make a Script-based workflow easy by defining the whole script in a module. Something like

module script
   somevar= 1
   somefunction(x) =x^2
   println(somefunction(somevar))
   ...
end

And then include the entire module each time the script is run.
That should keep things in order (although I haven’t tried this yet, and arguably seems like a “poor man’s revise”

1 Like

No, I save the file and press ctrl+enter, the keyboard shortcut for “run this in the REPL”. I had the same issue I’m describing even before I started using VS Code – It’s not a matter of forgetting to save, it’s a matter of not being able to remove junk from the workspace.

The first line of my startup.jl says using Revise;. That’s all I need to do to have it work, right?

I was thinking about this, but wasn’t exactly sure how to implement it. I think I may have figured it out, though! I can put my functions in one module, the entire test script in another module, and just include the test script from the REPL each iteration, just as you say. Re-including a module will wipe its previous binary, right?

This may trigger re-compilation of my work, but I’ll be able to keep the macros in my startup.jl that take so long to JIT!

You know what they say – every computer science problem can be solved by one more level of indirection, LOL

Update: Yep, this solution works! It allows you to have script-level variables without polluting the REPL!! This is exactly what I needed, thanks.

Now that I think about it, though, this does mean I lose the entry in the “workspace view” pane of VS Code, but I never really used it much anyways…

Update 2: I found a button that lets me see the variables in each module!
image

I find it incredibly silly that this isn’t all done under the hood when include-ing scripts from the REPL… I suppose some users might enjoy polluting their REPL with script variables…? Either way, I’m going to stick to this deterministic approach. Thanks!

No it’s not: you also have to tell Revise to keep track of your code. If you put in in a package which you then load, that happens automatically. If not, you need to replace every include with includet. See Revise usage: a cookbook · Revise.jl

2 Likes

Okay I took the nuclear option. Every single file I write has using Revise; as the first line and module arsldkjf; and so far it’s working as expected

I’m sure expert users/devs are internally screaming at this, but I’m not gonna get a Ph.D. in Julia just to be able to use the damn thing