Hacker News new | ask | show | jobs
by joiguru 1800 days ago
One point I have read repeatedly is that Julia is aimed at High Performance Computing (HPC). Another argument I have read repeatedly is how object oriented programming (with its single dispatch) is bad for HPC because of pointer indirections leading to cache misses. This leads me to conclude Julia will be much worse (than well written C/C++) due to its multiple dispatch.
3 comments

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).

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.
Julia is fairly fast, since its type system _only_ does dynamic/runtime typing, the JIT is optimized towards that. You'll experience some minor startup lag, typically due to initial JIT'ing of any new used functions. However, this has largely be remedied with a compiler backend that completely precomputes this behavior. https://julialang.github.io/PackageCompiler.jl/dev/
The key thing that resolves this is that for type stable code, Julia doesn't do dispatch at runtime. For most real world problems, Julia will know all the methods at compile-time, and therefore doesn't chase pointers at all.