Static.jl vs StaticNumbers.jl

I recently learned that there’s a StaticNumbers.jl from @Per that seems very similar to the SciML Static.jl (mostly actively @Zach_Christensen).

Are there known tradeoffs between these? Could there be a possibility to get the best of both in a single package?

1 Like

I can see from the code that StaticNumbers does things like subtyping Integers which then invalidates a ton of Base functionality. This is something Static.jl used to do, until we realized it contributed more than 2 seconds to load time IIRC, so that was cut out.

Static.jl is really just trying to be a very practical package, looking at the current landscape, and doing as much as it can and being as nice as it can without hitting absurd levels of invalidation and recompilation. Because of that, it makes a few trade-offs. That said, the older versions of Static.jl which does all of the nice things (a few more than StaticNumbers.jl seems to do) added over 8 seconds to load times for OrdinaryDiffEq.jl, so :person_shrugging: :sweat_smile: that had to be tamed.

In the end, I think these packages are mostly stopgaps. Better compile support for static handling, such as doing things like making Val(1) + Val(1) == Val(2) (i.e. making every operator, if applied with all arguments Val, operating on the values statically), is probably what’s required to make this all truly useful in the end. Without it, you need a custom system image.

3 Likes

We also decided early on to be more strict about which numbers would get explicit static type support in Static.jl because there’s not a whole lot of benefit in supporting three different static float types

1 Like

StaticNumbers.jl is a lot older than Static.jl, and at the time, there was not so much discussion about invalidations affecting load time. Also, constant propagation in Julia was not nearly as good as it is now. I think it is really cool that something like StaticNumbers.jl is possible to do in Julia, but that’s mostly theoretical, and In practice I tend to use Val instead in my own code in the rare cases where constant propagation is not enough.

What I really would wish for in the core language is a feature that lets you branch on whether a value is known at compile time, without having to use a Static or Val type at all.

4 Likes

This question was also touched in yesterdays long discussion in the #gripes slack channel (:
Static.jl puts more weight on loading/compilation times, sacrificing some functionality for that. For example, it recently stopped subtyping static(1) isa Integer. Meanwhile, StaticNumbers.jl still provides static Integers, making them more compatible with generic code. There may be other similar differences as well.

1 Like

Thanks, I hadn’t seen that. Yeah, the new Static.jl types are unfortunate. In the MeasureTheory ecosystem it forced us to stick to 0.6, mostly because we need things like static(0.3) isa Real and static(3) isa Integer.

From the Slack discussion it seems like a lot of other people might be in the same boat. Maybe we could get a registered fork with proper subtyping?

Maybe also worth mentioning here; the new @assume_effects macro can be used to enable very aggressive constant-propagation in some cases. For example:

# Example function
fib(n) = n<=2 ? 1  : fib(n-1) + fib(n-2)

# Make fib(::Val) compute at compile-time
Base.@assume_effects :terminates_globally fib(::Val{N}) where {N} = fib(N)

# Check the result
@code_llvm fib(Val(40)) # ret i64 102334155
1 Like

Sure, but sometimes we really need type-level values. Some discussion of that in

On the question of invalidations… If there’s a library that’s shown to be useful but can’t meet some performance criteria (invalidations causing long load times), that seems like evidence of a fundamental language problem. Especially for a language as performance-oriented as Julia, I’d think either there a fast way to do something, or Julia should adapt to enable a fast way to do it.

For me, StaticNumbers turned out to be enough, but the fork idea also sounds good. In principle, one could stay on an older version of Static forever, but this would likely lead to conflicts given Static.jl many dependents.

We won’t be able to depend on any package using StaticNumbers without destroying our hard work improving load times.

This is why I think it’s not an ecosystem issue, but a fundamental language issue. MeasureTheory needs this functionality, but of course we don’t want the load time issues (though I haven’t seen any such issues so far).

EDIT: What we mostly need is StaticFloat64 <: Real, since sooo many functions in the wild have ::Real constraints. We could maybe work around the current limitation, but it would be extremely hacky. More discussion here:

The current intention of constant propagation does not cover the full utility of static values. I did a long winded explanation and demonstration of this here Create staticint.jl by Tokazama · Pull Request #44538 · JuliaLang/julia · GitHub. The short version is that @assume_effects and more aggressive constant propagation won’t ever be a full substitute for static values.

1 Like

Agreed. What you really want just isn’t possible in a package right now. It needs compiler support or it has to be in the system image unless you are willing to sacrifice all precompilation. That’s a more fundamental language issue that these packages won’t be able to solve.

3 Likes

We could do a separate package that is StatocFloats because most of the load time issues for packages depend on StaticInt

1 Like

@Zach_Christensen has already mentioned his PR around this, which I hope can move forward. Do you think static floats will also need base support, or could there be a path toward StaticFloat64 <: Real in a library without invalidation issues?

EDIT: Zach beat me to the punch :slight_smile:

A StaticFloats.jl would help a lot! I think I could make due with the StaticInt support in Static.jl until there’s Base support for that

Invalidations are pretty hard to predict for something as fundamental as floats and integers. It’s usually easier to solve for floats because they aren’t involved in indexing, which is everywhere (indexing related behavior is probably 90% of what we use StaticInt for too :frowning:). The implementation of StaticFloat4 can be completely independent if we subtype Real, but then it is kind of obnoxious for Static.jl to continue to grow in size (and load time) when it was never intended to be a very large package.

2 Likes

Right, so maybe there should be a new StaticFloats.jl owning StaticFloat64, and that code is removed form Static.jl?

It might be the best solution for the time being. We just would need to make sure its a manageable breaking change forst

1 Like

For the same reason, adding the code that would be in a package directly into Base via some ststicint.jl file to prevent invalidation is not going to solve the situation, and will, at best, just be a bandaid for one specific usecase. Personally I don’t think that’s a good idea, and the reason can be seen in this very thread: what about floating points values? And then the next “what about” and so on and so on.

1 Like