Avoid array copies in the calc_correction accumulation loop - #169
Avoid array copies in the calc_correction accumulation loop#169achubaty wants to merge 1 commit into
Conversation
|
@achubaty I'll be happy to give you commit access here if you are comfortable with the codebase. While I am familiar with Circuitscape.jl, I am not familiar with Omniscape.jl - and it is great to see you opening PRs. Let me know if you are comfortable with commit here. |
|
@ViralBShah would commit access to non-main branches as a start make things a bit easier, or is there not much point in that? I think we still want reviews and to keep main protected. I know I'm saying this as someone who hasn't touched the project in a long while, so I don't want to get in the way either! I'm happy to provide some general support on PR reviews, though I do want to make a disclaimer that I'm quite out of practice with Julia these days. |
|
@vlandau That makes sense. I was hoping to see if someone could claude their way into updating the package to 1.12 and fix some of the tolerance issues cropping up. |
|
@ViralBShah yeah that may be the likeliest path to an update (finally). Will just want to make sure the code is reviewed when AI-contributed. The very nice thing here though is that we have canonical correctness tests, so we can feel confident on changes if tests pass. Guards us a bit against AI errors (but not necessarily overly verbose and over-engineered AI slop that you can see when larger tasks are dispatched to a model like opus 5 (in my experience)). I will think about this. Perhaps I will be able to do a short hackathon to update the package over the weekend some time. |
|
@ViralBShah Thank you for the offer of commit access. I agree with @vlandau that non-main level access is preferred, so I can make small changes and have them reviewed as I'm still getting my head around things here (and using Claude). I will need to update this PR to rebase using the updated julia@v3 action. THis is forthcoming |
Indexing with ranges on the left of += does not write in place. Each iteration copies the target region out, allocates the sum, and writes it back, so the loop allocates two arrays of (2*radius + 1)^2 elements every time it runs -- block_size^2 times. Using @views with .+= performs the same additions in the same order without the temporaries. At radius 503 with block_size 51 the loop allocates 39.3 GiB before the change and 8.5 MiB after, and runs roughly 8 times faster. It runs before the moving window loop starts, on a single thread, so parallelism does not reduce it. Results are unchanged bit for bit, checked by comparing the raw bit patterns of the two arrays. Test suite passes 10/10 and 31/31 on Julia 1.11.7 with Circuitscape 5.15.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
706ba26 to
740a2d2
Compare
|
The red checks here are the same infrastructure problem I described on #168, not anything to do with this change. The run died in dependency resolution, before a single test executed. From the ubuntu job's log: Briefly: this run used I have rebased onto current One thing worth flagging: unlike #168, this rebase will not turn CI green on its own. This branch carries no Circuitscape pin, so it resolves 5.17.1 and will now get past resolution only to hit the three failures |
Problem
calc_correctionbuildsnull_current_totalby adding the same array into overlapping regions, once for each cell of the block:Indexing with ranges on the left of
+=does not write in place. Each iteration copies the target region out, allocates the sum, and writes it back — two arrays of(2*radius + 1)²elements per iteration,block_size²times.This runs before the moving window loop begins, on a single thread, so adding threads does not help.
Change
The same additions happen in the same order; only the temporaries go.
null_current_totalandnull_currentare separate arrays, so there is no aliasing to worry about.Results
Measured with the loop lifted out on its own, on Julia 1.11.7,
buffer = 0.identicalcompares the raw bit patterns of the two result arrays, not≈.The allocation figures are exact and repeatable. The times were taken on a shared machine that was busy at the time, so read them as roughly an order of magnitude rather than as precise ratios.
At the sizes used in the test suite (
radius = 5) the difference is not measurable either way.Test suite on Julia 1.11.7 with Circuitscape 5.15.0:
Internals10/10,run_omniscape()31/31.Script that produces the table
Standalone — no Omniscape state, nothing outside the standard library.
A larger change, if you want it
The loop computes a two-dimensional convolution of
null_currentwith ablock_size × block_sizebox. A box filter is separable, so two passes of a sliding-window sum give the same result in far less work — about 650 times faster than the current code atradius = 503, against about 8 times for the change proposed here.I have not included it. It changes the order of summation, so results differ by around 1e-13 relative, and the stored reference rasters would need to be looked at. Happy to open it separately if that is of interest.
🤖 Generated with Claude Code