Skip to content

unwrap: Initialize image reliability in deterministic order #617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 14, 2025

Conversation

RomanHargrave
Copy link
Contributor

@RomanHargrave RomanHargrave commented Dec 28, 2024

init_image() presently calls rand() once per pixel to initialize reliability values. As this is done inside an @threads loop, this does not happen in a deterministic order, in turn causing unwrap to produce measurably different outputs when executed for the same input and with a prepared PRNG. To remedy this, I moved reliability value generation outside of the loop, which stabilized the output - now there is zero variation between applications (with a fresh PRNG).

Unrelated, but the use of @threads here seems like it is of limited benefit - there does not seem to be any computationally intense work inside the loop.

Fixes #616

@RomanHargrave RomanHargrave changed the title unwrap: Initialize image reliability in deterministic order (Fixes #616) unwrap: Initialize image reliability in deterministic order Dec 28, 2024
@RomanHargrave RomanHargrave force-pushed the roman/deterministic-unwrap branch 2 times, most recently from 508a6aa to 23f4da1 Compare December 29, 2024 00:15
Copy link

codecov bot commented Dec 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.96%. Comparing base (73d58fb) to head (3de3387).
Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #617   +/-   ##
=======================================
  Coverage   97.96%   97.96%           
=======================================
  Files          19       19           
  Lines        3250     3251    +1     
=======================================
+ Hits         3184     3185    +1     
  Misses         66       66           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@wheeheee wheeheee force-pushed the roman/deterministic-unwrap branch from d40855b to 2e77879 Compare December 30, 2024 04:31

f_uw = f_wraparound2.(grid, grid', reshape(grid, 1, 1, :))
f_wr = f_uw .% (2convert(T, π))
uw_test = unwrap(f_wr, dims=1:3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while doing a bit of cleanup i stumbled upon this. i think it should have been called with circular_dims? so I amended it.

Copy link
Member

@wheeheee wheeheee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than that test I'm not completely sure about, I think this is ok and I'll merge this in a while.
@martinholters any comments?

Copy link
Member

@martinholters martinholters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but the use of @threads here seems like it is of limited benefit - there does not seem to be any computationally intense work inside the loop.

Agree. Now it's just the allocation of Pixels that happens threaded, I believe? Is that even beneficial, or does the allocation then serialize things again by using locks anyway, and it's just the trivial initialization that runs in parallel?

Comment on lines +149 to +155

# Initialize reliability values before going parallel. This ensures that
# reliability values are generated in a deterministic order.
rels = rand(rng, Float64, size(wrapped_image))

Threads.@threads for i in eachindex(wrapped_image, pixel_image, rels)
pixel_image[i] = Pixel(wrapped_image[i], rels[i])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this @threads even worth it or could we just keep the old code but with Threads.@threads removed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, missed that. Ran some quick benchmarks, and not sure why, but on average it seems to be slightly faster with the @threads, but it's within the error.

julia> @benchmark unwrap(A; dims=1:2) setup=A=rand(500,500)
BenchmarkTools.Trial: 47 samples with 1 evaluation.  # PR
 Range (min  max):   90.717 ms  143.875 ms  ┊ GC (min  max):  0.00%  32.85%
 Time  (median):     103.035 ms               ┊ GC (median):     8.80%
 Time  (mean ± σ):   105.516 ms ±  12.979 ms  ┊ GC (mean ± σ):  11.29% ±  9.20%

  █   ▅         ▂                               ▂
  ██▁██▁█▁▅█▁█▅████▁▅█▁▁▅█▁▅▅▅▁▁▁▅▁▁▁▁▅▁▁▁▁▁█▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▅ ▁
  90.7 ms          Histogram: frequency by time          144 ms <

 Memory estimate: 60.97 MiB, allocs estimate: 250108.

BenchmarkTools.Trial: 44 samples with 1 evaluation.  # PR without @threads init
 Range (min  max):   92.638 ms  151.973 ms  ┊ GC (min  max):  0.00%  31.74%
 Time  (median):     109.116 ms               ┊ GC (median):    13.32%
 Time  (mean ± σ):   113.244 ms ±  17.772 ms  ┊ GC (mean ± σ):  14.86% ± 11.17%

  ▂ █           ▂   ▂
  █▅█▅▅▅▁▁█▅▁▁▁▁█████▁█▁▁▅▁▁▁▁▅▅▁▅▅▁▁▅▅▅▁▁▁▁▁▁▁▁▅▁▁▁▁▅▁▁▅▁█▁▁▅▅ ▁
  92.6 ms          Histogram: frequency by time          152 ms <

 Memory estimate: 60.96 MiB, allocs estimate: 250066.

julia> @benchmark unwrap(A; dims=1:3) setup=A=rand(50,50,50)
BenchmarkTools.Trial: 77 samples with 1 evaluation.  # PR
 Range (min  max):  56.739 ms  95.696 ms  ┊ GC (min  max):  0.00%  39.25%
 Time  (median):     65.154 ms              ┊ GC (median):    10.08%
 Time  (mean ± σ):   64.903 ms ±  7.279 ms  ┊ GC (mean ± σ):   9.90% ±  8.89%

   █▃▃
  ▇███▅▄▁▅▄▁▄▄▁▁▁▅▄▁▄▁▁▄▅▄▅▅▇▄▄▇▁▅▅▁▇▄▅▄▄▄▅▇▁▁▁▄▁▄▁▁▁▁▁▁▁▁▄▄▅ ▁
  56.7 ms         Histogram: frequency by time        78.8 ms <

 Memory estimate: 39.94 MiB, allocs estimate: 125108.

BenchmarkTools.Trial: 72 samples with 1 evaluation.  # PR without @threads init
 Range (min  max):  58.111 ms  106.682 ms  ┊ GC (min  max):  0.00%  42.53%
 Time  (median):     64.032 ms               ┊ GC (median):     0.00%
 Time  (mean ± σ):   68.962 ms ±  11.368 ms  ┊ GC (mean ± σ):  12.67% ± 12.33%

  █▅▅
  ███▅▆▅▃▃▅▃▁▁▁▁▁▁▁▅▅▆▃▅▅▆▆▃▆▃▁▁▅▃▁▁▁▃▁▁▁▁▁▁▅▃▁▁▁▃▁▁▁▁▁▁▁▁▁▁▃▃ ▁
  58.1 ms         Histogram: frequency by time          100 ms <

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I'd tend to the simpler implementation, but no strong opinion here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just keep it. In fact I think we could also add Threads.@threads to for i in fi:li in the first loop of calculate_reliability...

@wheeheee wheeheee force-pushed the roman/deterministic-unwrap branch from 2086623 to 3de3387 Compare January 10, 2025 01:54
Copy link
Member

@martinholters martinholters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea about the other test changes, but the main point of this PR looks good to me.

@wheeheee wheeheee merged commit 1f70965 into JuliaDSP:master Jan 14, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

unwrap is not stable when supplied with stable RNG
3 participants