Identical method redefinition suspiciously optimizes runtime and allocations

afleming
Sun, 07 Sep 2025 11:42:14 +0000
Performance

I am using Revise.jl to work on a package for my research, and am noticing that “revising” a file to identical code with different variable names causes a function to stop allocating.

I am not sure how many details others will need to help me diagnose the issue, but my workflow is as follows:

1. Run a script using Julia and the -i flag. startup.jl consists only of using Revise.

2. The script loads some data files, as well as BenchmarkTools.jl and Plots.jl

3. Benchmark some functions that need to be allocation-free (for performance reasons).

The offending function is:

function is_cell_contained_by(cell1, poly)
    cell_poly = cell_boundary_polygon(cell1) # this is an SVector
    return all(edge_starts(cell_poly)) do pt # edge_starts returns an SVector
        return PlanePolygons.point_inside_strict(poly, pt) # this does not allocate
    end
end

And benchmarking it:

julia> @benchmark Euler2D.is_cell_contained_by($test_cell, $test_poly)
BenchmarkTools.Trial: 10000 samples with 709 evaluations per sample.
 Range (min … max):  180.385 ns …  51.612 μs  ┊ GC (min … max): 0.00% … 99.40%
 Time  (median):     194.587 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   237.031 ns ± 556.060 ns  ┊ GC (mean ± σ):  8.90% ±  8.09%

   ▃▇█▅▄▁                           ▂▂▂▃▃▂▂▁▁                   ▂
  ███████████▇▇▆▆▆▆▄▃▅▃▁▁▄▄▁▄▁▃▄▁▁▁▇██████████▇▆▆▃▄▃▁▁▁▃▄▁▃▃▄▃▄ █
  180 ns        Histogram: log(frequency) by time        437 ns <

 Memory estimate: 464 bytes, allocs estimate: 5.

Well, that’s odd. Edit the file defining is_cell_contained_by(...) and let Revise.jl do its magic. I change the function to:

function is_cell_contained_by(cell1, poly)
    cell_polys = cell_boundary_polygon(cell1)
    return all(edge_starts(cell_polys)) do pt
        return PlanePolygons.point_inside_strict(poly, pt)
    end
end

Notice that only the variable name has changed: cell_poly → cell_polys. Benchmark it again:

julia> @benchmark Euler2D.is_cell_contained_by($test_cell, $test_poly)
BenchmarkTools.Trial: 10000 samples with 985 evaluations per sample.
 Range (min … max):  53.646 ns … 108.297 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     57.632 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   57.934 ns ±   2.129 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

                  ▂▁▁▁▄▆█▇▇▅▁▁  ▁        ▁▁▂▁ ▁▁ ▂▁            ▂
  ▄█▆▇▃▃▁▁▁▆▇▃▅▇▅▇█████████████████▆▆▅▄▆▇███████████▇▆▆▆▅▄▅▆▆▆ █
  53.6 ns       Histogram: log(frequency) by time      63.8 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

This is the behavior I expect… but I do not know why I have to force Revise.jl to pick up on the new function every time I run the script.

Is there a cache I can clear somewhere? Should I kill every running Julia process on my machine and try again? I am absolutely baffled, and I hope that I’m just falling into a common Revise.jl trap, rather than doing something truly stupid.

26 posts - 6 participants