Request: Julia debug mode

Can you sport were I got lots of allocations? I don’t need a debugger, right now, I found the bug, this is just a motivating example, and I think a debug mode would be better than a debugger.

function time_code2(A)
  code = encode_64_full((A[i], A[i+1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
  for i in eachindex(A)[1:64:end]
    decode_64_full(code)
  end
end

julia> @time time_code2(A_orig2)
  0.002365 seconds (10.00 k allocations: 5.189 MiB)

The loop count is 10000 (like the allocations). In case you don’t see it, here’s the original (I was trying to simplify the loop to only time decode_64_full, moving encode_64_full out of it):

function time_code(A)
  s = 0; for i in eachindex(A)[1:64:end]
    decode_64_full(encode_64_full((A[i], A[i+1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))) # A[i:i+63]
  end
end

julia> @time time_code(A_orig2)
  0.009769 seconds

Can you see it now:

function time_code3(A)
  code = encode_64_full((A[1], A[1+1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
  s = 0; for i in eachindex(A)[1:64:end]
    decode_64_full(code)
  end
end

julia> @time time_code3(A_orig2)
  0.000026 seconds

I was staring at the code for a while.

The original title was β€œJulia without global variables”, then I changed my mind to β€œJulia without global variables, a debug mode”. I didn’t want to give away where the bug is.

For these kinds of issues, I recommend JET:

julia> using JET

julia> function time_code2(A)
         code = encode_64_full((A[i], A[i+1], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
         for i in eachindex(A)[1:64:end]
           decode_64_full(code)
         end
       end
time_code2 (generic function with 1 method)

julia> @report_opt time_code2([1,2,3])
═════ 5 possible errors found ═════                                                                       β”Œ @ REPL[2]:2 A[%1]                                                                                       β”‚ runtime dispatch detected: (A::Vector{Int64})[%1::Any]::Any
└─────────────
β”Œ @ REPL[2]:2 %3 + 1
β”‚ runtime dispatch detected: (%3::Any + 1)::Any
└─────────────
β”Œ @ REPL[2]:2 A[%4]
β”‚ runtime dispatch detected: (A::Vector{Int64})[%4::Any]::Any
└─────────────
β”Œ @ REPL[2]:2 %7(%6)
β”‚ runtime dispatch detected: %7::Any(%6::Tuple{Any, Any, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64, Int64})::Any
└─────────────
β”Œ @ REPL[2]:4 %69(%8)
β”‚ runtime dispatch detected: %69::Any(%8::Any)::Any

Indeed, it would be nice if it was somehow easier to have JET-like functionality without having to type in every function call. It’s not obvious how to achieve that in a language so generic as Julia though,

2 Likes

Thanks, I knew of JET.jl, but I’m not sure it’s a substitute for a debug mode. I’m a bit surprised by β€œ5 possible errors found” and none of them are very readable to me yet, nor pointing to the smoking gun. And for me (I would have given up, but useful(?) to know you get a restricted list without the dependent functions):

julia> @report_opt time_code2([1,2,3])
═════ 50 possible errors found ═════

In case it’s not obvious to people, then I should have known, A[i] is no longer in the scope of the loop, is i is now pointing to a global variable, would be a runtime error had it not been also defined. But I didn’t think of that line in relations to allocations.

So what I propose is a Julia mode without global variable access. I suppose having globals is ok for the REPL.

help?> global
[..]
  julia> function foo()
             global z = 6 # use the z variable defined outside 

Maybe this is actually a bug in Julia, or why else allow being explicit about a global being used?

Some but very few languages (V) have no global variables, and while e.g. C and C++ to have global variables you’re less likely to define them and allow the function to compile by accident than in a REPL language.

I have some other ideas this debug bode could do. Such as disable @inbounds, @inline and run with -O0. You could also call it a scripting mode. Just being able to disable globals though (or separately) seems like a good option, but doing it by default would be a breaking change for 2.0, unless of course that’s already intended?