Why isn't `size` always inferred to be an Integer?

Converting it to an Int? I mean, if you know that is what it should be, let it be. :stuck_out_tongue:

Also, a function barrier may work? Don’t know the rest of the code so IDK.

1 Like

I don’t think the compiler reads the docs. If it can’t prove it, it has to be Any. I mean, how else could it work?

1 Like

Never fully Groked the concept but isn’t this type piracy? And if yes, why comply with it?

No, if Bad is your own type, you actually have to make your own size.


Knowing it’s gotta be an Integer is, really, not any better than Any for the purposes of the compiler and possible runtime speed. It still needs the costly indirection to figure out what instructions to use, and it can’t just shove it into a 64-bit register.


This is not type-piracy. The type Bad is defined inside the actual “module” and you define Base.size on it.

IIUC type piracy is that you define an external function to your module to a type external to it. Something like this

module A
function f() end

module B
struct MyType end

module C
import ..A: f
import ..B: MyType

function f(x::MyType) end # type piracy

Thanks, this I can understand.

And on the same spirit of this thread, how can a string return a Any

└────         goto #157
155 ─ %823  = GMT.string(val)::Any
│     %824  = (%823 != "indata")::Any

Same, even though we “know” the interfaces, the compiler cannot guaranteed always that they are respected.

I think you may be taking this output too seriously. Does it actually cause type instability to propagate in your package?

It’s not literally returning Any — the compiler just isn’t sure what it might return at runtime. And it’s because the compiler doesn’t know what type val will be, so it doesn’t know what method of string will be called.

Just as in the size example, the trouble isn’t so much string (or size), but upstream of that — sort out the stability of their arguments and they’ll then be stable.


Maybe not, but what I know is the module in question is precompiled and it still takes a further ~6 seconds to run on the first run. I try to reduce that and the only thing I can tie to the the potential instabilities … and ~zero success.

As @mbauman says it’s something else in the function thats unstable. You can fix that, or if thats not possible split the function into 2 functions to create a function barrier, so as much of it as possible is stable.

I know that, but in this case it’s an impossible task because those are derived from input arguments to the function and they can have different types. Nothing really important for run time … as long as per-compilation had worked well. But it didn’t and I’m just trying to find out why and the Any's are the beasts to chase, so we are told.

And to give it more context, this the function I’m referring to GMT.jl/psxy.jl at master · GenericMappingTools/GMT.jl · GitHub. The source of the Any's is the kwargs tht are converted into a Dict(:symbol, Any) and from there anything extracted from the Dict is a Any. Have no idea on how to work around this.
But again, this does not seem to hurt runtime … after compilation of first run, hence latency.

Type instabilities in end-packages tend to impact runtime speed, not the (pre-)compile time. In upstream libraries, yes, they can indeed become one of the magnets for invalidation, but I don’t think they’re typically troublesome at the point where I think you are.

This conversation is now circling back to the original thread from which it was split — we can go back to Taking TTFX seriously: Can we make common packages faster to load and use for concrete tips for reducing that compile time.

@mbauman dont type instabilities also account for a lot of (pre)compile time, because its more work for the compiler to resolve types?

In my experience fixing type stability absolutely reduces compile time.

1 Like

You’re absolutely right — I was wrong. As Chris notes in his magnum opus of DifferentialEquations.jl #786:

The last thing, and the major step forward, was SciML/DiffEqBase.jl#698 (comment) . As it explains, using a function barrier can cause inference to not know what functions it will need in a call, which makes Julia seem to compile a whole lot of junk . That can probably get fixed at the Base compiler level to some extent, as the example there was spending 13 seconds compiling (::DefaultLinSolve)(::Vector{Float64}, ::Any, ::Vector{Float64}, ::Bool) junk which nobody could ever call, since any call to that function would specialize on the second argument (a matrix type) and so it should’ve been compiling (::DefaultLinSolve)(::Vector{Float64}, ::Matrix{Float64}, ::Vector{Float64}, ::Bool) . But since that call was already precompiled, if inference ends up good enough that it realized it would call that instead, then bingo compile times dropped from 16 seconds to sub 3. That’s a nice showcase that the easiest way to fix compile time issues may be to fix inference issues in a package, because that can make Julia narrow down what methods it needs to compile more, and then it may have a better chance of hitting the pieces that you told it to precompile (via a using-time example in a precompile.jl).


Yeah thats my experience too. You can see the pyramids of compilation calls shrinking and disappearing from the flame graph as you remove unstable code. Boxed vars need much more llvm/native code than Ints and Floats.

Why do you need to convert kwargs to a Dict? Can’t that function use them as-is, or at least convert to a namedtuple (it’s type-stable)?

1 Like

The very truth is that when I started this around 2015 I was so happy to have achieved the goals of being able to easily parse multi-type inputs that didn’t look back. Since then the code grew quite a bit and now is not at all obvious to do changes. However, more recently I made experiments in parsing the kwargs directly and it made no difference in what respect the Any's side. Basically, if one want to accept multi-type inputs I think there is nothing we can do other than to try limit the Any's propagation.