We are doing large-scale numerical simulations and discovered a reproducibility issue with our numerical code, where the output was different depending on whether we were producing plots on the fly or not. After an “exciting” afternoon of tracking down the problem, we boiled it down to the following minimal example.
(run in an environment where only the Colors
package v0.12.10 is installed)
> julia --optimize=1 --project=. -e 'c = 0.46309504630950465; display(cos(c)); display(sincos(c)[2])'
0.8946741679895334
0.8946741679895334
So far, so good.
But if we load the Colors
package:
> julia --optimize=1 --project=. -e 'c = 0.46309504630950465; using Colors; display(cos(c)); display(sincos(c)[2])'
0.8946741679895334
0.8946741679895333
This doesn’t happen if the sincos
function is used before loading Colors
:
> julia --optimize=1 --project=. -e 'c = 0.46309504630950465; sincos(0.1); using Colors; display(cos(c)); display(sincos(c)[2])'
0.8946741679895334
0.8946741679895334
With default optimization, we always get
> julia --project=. -e 'c = 0.46309504630950465; display(cos(c)); display(sincos(c)[2])'
0.8946741679895333
0.8946741679895333
which is different from optimize=1
but at least consistent across the two functions and does not depend on whether Colors
is loaded or not, or whether sincos
is used before using Colors
or not.
I couldn’t find anything obvious in the Colors package that overwrites methods of cos
or sincos
, it just uses them.
A number of questions arise:
- First the most critical question: Why does the
sincos
computation depend on whether a package was loaded before its first execution or not? How can we avoid such things for potentially other functions. This is a huge reproducibility issue for us if we cannot guarantee that the same basic math method called in the same environment always returns the same values (assuming, of course, there was no type piracy or methods overwritten by loaded packages). - Is it expected for this code to return different numerical values depending on optimization level? For very agressive optimization, I could imagine that the compiler makes wrong assumptions about side effects, etc, and thereby changes results, but here, just calling two very basic math functions with standard (or lower) optimization, I would have expected no differences.
- Whatever happens in
Colors.jl
, isn’t it inconsistent that that effect can be avoided by calling the function beforeusing Colors
? This makes it seem that this has to be quite a low-level issue (compiler, optimization, etc.). - Is it expected for
cos
andsincos…[2]
to return different values at all?
Colors.jl
uses some @fastmath
, etc., but merely using it shouldn’t change the results we get for the standard function without @fastmath
, should it?
In any case, after tracking it down to this basic level, we are a bit out of our depth on how to continue, so we would appreciate any input from the community. If any additional information is needed, please let us know.
Julia Version Info
Julia Version 1.9.3
Commit bed2cd540a1 (2023-08-24 14:43 UTC)
Build Info:
Official https://julialang.org/ release
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 12 × Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-14.0.6 (ORCJIT, skylake)
Threads: 1 on 12 virtual cores