[ANN] LightSumTypes.jl v4

Would the version with sumtype be faster?

It just stuck me that union of subtypes is different than its supertype. Nice gotcha, Thanks

IIUC, that is the whole point of sumtype :slight_smile: Essentially you change from having to do a dynamic dispatch on every access to a much cheaper if/else on the type - a “dynamic dispatch”-lite if you wish. This if/else is cheaper because there are only limited and fixed options whereas the full dynamics dispatch checks for all applicable methods, then figures out the most specific one (or errors if there is no single one) and then dispatches to that.

With Julia <=1.10, I’m sure it will, probably around an order of magnitude. With Julia >=1.11 I think it will around 1.5-2x faster in many cases with concrete subtypes, this is what I see on some realistic benchmarks on nightly, see Performance Tips · Agents.jl. This is because as I said in another comment Julia now seems to be able to actually use Union-splitting effectively fortunately!

Secondly, a @sumtype should be much faster to compile. This is something important I think because when you have a lot of types you will have big compile times with a normal Union.

cc @jameson this is the discussion I mentioned regarding the performance of Union of composite types or a special macro that composes if clauses instead of formal dynamic dispatch.

1 Like

Yes, that makes sense. I think we have fixed limits on how many call targets it will consider expanding, and the limit is something very small, even though the limit is probably not necessary in this case and could be removed from the compiler. That change should allow the compiler to automatically generate this if/else nest. Generating it manually is also good, but just perhaps annoying to need to specify explicitly when writing the code.

2 Likes

thanks @jameson for your comment, but I’m not really sure I understand what “call targets” means, does it mean that Julia can give up on doing union-splitting in some calls to a function which would require that to avoid a dynamic dispatch? I see that in Julia 1.11 it doesn’t give up so easily though, I didn’t find a clear case where this happens. Also, apart from runtime performance, I see also compile time performance increasing by wrapping the Union when it is not necessary to compile a different function for each subtype, is this something also possible to emulate in Julia Base?

Yes, there is some threshold values that it uses to evaluate whether the dispatch optimization seems profitable or accidental. It might be interesting to know why the compile time is significantly faster, though I suspect some of it is because the explicit struct wrapper does make the job much simpler for inference, which would otherwise need to decide if that Union was profitable or accidental much more frequently. Emulating that with a typealias to some variant on Some{Union{...}} is generally expected if the user wants to avoid the compiler making those heuristic evaluations and specifying those explicitly instead.

3 Likes

It looks like this is not supported:

struct Some{T}
   val::T
end

struct None end

@sumtype Option{T}(None, Some{T})

Nor are parameterized sumtypes supported in general.

There are a lot of use cases that require parameterized sum types.

However, the code is really simple, and it looks like it would be rather easy to add support.

3 Likes

yes, this would actually be really useful. I thought (badly) that

@sumtype Option(None, Some{Some_Concrete_T})

would suffice…but obviously it does not because you can’t dispatch on the parameters of the sumtype itself because there is no parameter :sweat_smile:

So…I already implemented it. Hopefully I didn’t get something wrong: Make it possible to use parameters in sumtype by Tortar · Pull Request #105 · JuliaDynamics/DynamicSumTypes.jl · GitHub

2 Likes

Hi all!

I released a new version (4.0.0) of the package which has a slightly different name, because the old one was a bit confusing to some people, and I agree that it was a misnomer. Now the library is called LightSumTypes.jl. “Light” seems better than “Dynamic” for what the library does.

Also, not too much time ago I pushed a change in the code generation and now LightSumTypes.jl is on par with Moshi.jl both on 1.10 and 1.11 in the performance of the micro-benchmark used there: see Benchmarks | Moshi and for those who would like to use pattern matching on sumtypes I verified that the @match macro in Moshi.jl can be used for pattern matching without any performance drop as far as I can tell:

julia> using LightSumTypes

julia> using Moshi.Match: @match

julia> struct A end

julia> struct B x::Int end

julia> @sumtype S(A, B)

julia> s = S(B(4))
S(B(4))

julia> @match variant(s) begin
           A() => 1
           B(x) => x
       end
4
11 Likes