Avoiding Vectors of Abstract Types

It’s useful to distinguish two related concepts:

  • union splitting: specializing call sites when you know the type can only be one of a specific, finite list
  • what I have been calling “world splitting” (specifically the section starting at 6:48, or more narrowly starting at 10:40): specializing call sites for the methods that have been defined at the time of compilation

Keep in mind that the second is open-ended, whereas the former is closed. That’s a huge difference. While “open-ended” gives you greater flexibility and may be necessary in some cases, it opens you to much longer compile times and invalidation. Over time, Julia has gotten more restrictive: the world-splitting limit dropped from 4 types to 3 types, which is the reason your tall yellow bars above start once you have 4 methods. This is because the fewer methods you have to speculatively infer, the faster compilation becomes. (IIUC correctly, if you raise the limit to 5, no one has had the patience to wait for Julia to finish compiling itself, and it’s possible it never does.) While I understand that you might wish it were higher “for just this one function,” the reality is that these parameters have huge implications for the usability of Julia in the broader ecosystem. Several of us are quite excited about a change on master that will allow package authors to drop the limit even further (you can cut the TTFP for Plots in half by dropping the limit to 1).

The moral of the story: if you care about performance, you’re best off using real Union-splitting, rather than relying on world-splitting. In cases where you need to intervene, your best option is to add isa(x, T) blocks and do the dispatch manually. Your performance will be good here only if T is a concrete type, since abstract/partial types will invoke julia’s subtyping machinery, whereas comparing to a concrete type is done by pointers (i.e., very fast).

11 Likes