Hacker News new | ask | show | jobs
by celrod 1800 days ago
In practice, Julia's multiple dispatch is almost always devirtualized. That is, dispatch is resolved at compile time.

Generic code relies on specialization instead of dynamic dispatches to be generic with respect to input types. That is, for each new input type, a new method gets compiled (allowing the dispatch to be resolved statically).

2 comments

Do you have a citation for this? My google searches have led me to believe this to not be true; see [1]

Here is the relevant quote from [1]: "In the context of julia though, compile time type simply do not exist ...."

[1]: https://discourse.julialang.org/t/claim-false-julia-isnt-mul...

This is one of the cases where there's sometimes confusion between the semantics of Julia as a language and the practical implementation of Julia -- just like how Julia is dynamically typed as a matter of language semantics, but compiles to a statically-typed intermediate representation as an implementation detail [1].

While it is not part of the language semantics, there certainly is, in the current implementation, a time at which any given method in Julia is (JAOT) compiled (via SSA-form IR, LLVM IR, and finally to native machine code) -- and whether or not types are able to be inferred at this time is sufficiently important that it has its own name: type stability [e.g., 2], with type-stable code being generally a couple orders of magnitude faster than equivalent type-unstable code.

[1] https://stackoverflow.com/questions/28078089/is-julia-dynami...

[2] https://www.juliabloggers.com/writing-type-stable-julia-code...

For those who don't know JAOT stands for Just-Ahead-Off-Time and refers to the fact that Julias current implementation doesn't do many things associated with JITs today but simply calls an AOT compiler under the assumption that any function gets called often enough in this session for compilation to be amortized.

There are some weak mechanisms to prevent useless overspecialization such as @nospecialize and there are attempts to add smarter recompilation strategies by some packages.

Julia semantically doesn't have compile time type, but is free to (and almost always does) figure out the compile time type and use that information aggressively as long as doing so doesn't change behavior.
By "compile time", I meant the first time you call the function with a given type signature.

Also, that comment is saying "compile time type" does not exist. I don't know C++, so I cannot comment on it, but from the sound of Yu Yichao's comment, C++ has separate concepts for runtime vs compile time types. Julia does not (as already said by adgjlsfhk1).

Is it resolved at compile time? it was my understanding that Julia only attempts it at runtime, specialization being the job of the JIT
Depends what you mean by compile time.

If function f(...) calls g(...) then the first time you call f(...), g(...) would normally also get compiled. The compilation is specialized on the type of input arguments (the method is selected by multiple dispatch and code is specialized further by concrete type information).

If the concrete types of ... in the g(...) call are inferrable, then g(...) is specialized and compiled to native code immediately, and possibly even inlined. If those types can't be inferred, it will repeat this process when it is called (there is also a cache of specialized code so it only compiles once, but you need to "look up" the function in cache dynamically in that case).

In many situations the types of an entire program can be inferred and be "compiled" ahead of time, but the semantics are always that of an interpretter.

TLDR here is that Julia is better described as Just Ahead of Time (JAOT) instead of JIT. Julia isn't using a normal tracing JIT where things start running interpreted and then get replaced. When Julia first runs into a new function (or function being called with new argument types), it will statically compile (at runtime) code for that function being called with those argument types. When it does this, it will use the type information of the types the function has been called with, to do all sorts of optimization (de-virtualization, inlining, etc).

Once this method is compiled, it will be used whenever the same function is called with the same argument types.

Julia compiles at run time.