The (missing) harm of manual dispatch in Julia

September 24, 2020

Just a short one today: New users of Julia coming from other dynamically typed languages might write functions like

function foo(x)
    if x isa Int
        x + x
    elseif x isa Float64
        x / 2.0
    elseif x isa String
        length(x)
    else
        1
    end
end

This is clearly unidiomatic and should be written as

bar(x::Int) = x + x
bar(x::Float64) = x / 2.0
bar(x::String) = length(x)
bar(x) = 1 # or bar(x::Any), to be explicit

because it is nicer to read and very easy to extend for other types of x. Well, you technically can extend foo the same way as bar, but reading the definition of foo one would expect to then know its complete behavior, so it would be misleading.

However, my point is that there is actually (maybe surprisingly) no harm at runtime for code as in foo! The Julia compiler isn’t tricked that easily and will still produce optimal machine code for each type of the argument:

julia> @code_native debuginfo=:none bar(1)
    .text
    leaq	(%rdi,%rdi), %rax
    retq
    nopw	%cs:(%rax,%rax)

julia> @code_native debuginfo=:none foo(1)
    .text
    leaq	(%rdi,%rdi), %rax
    retq
    nopw	%cs:(%rax,%rax)

Both functions basically compile down to a single adding instruction (leaq; retq is for returning from the function and nopw is an operation that does nothing and is there for technical reasons). Think about it this way: When Julia compiles foo(1), it knows that 1 is of type Int, can evaluate the x isa Int expression at compile time to true, and discard everything but the x + x without a problem.

“Appendix”

For completeness, here is what’s produced in the other cases:

julia> @code_native debuginfo=:none bar(1.0)
    .text
    movabsq	$140581530607976, %rax  # imm = 0x7FDBB031A168
    vmulsd	(%rax), %xmm0, %xmm0
    retq
    nop

julia> @code_native debuginfo=:none foo(1.0)
    .text
    movabsq	$140581530608088, %rax  # imm = 0x7FDBB031A1D8
    vmulsd	(%rax), %xmm0, %xmm0
    retq
    nop


julia> @code_native debuginfo=:none bar("abc")
    .text
    pushq	%rax
    movabsq	$"ncodeunits;", %rax
    callq	*%rax
    popq	%rcx
    retq
    nop

julia> @code_native debuginfo=:none foo("abc")
    .text
    pushq	%rax
    movabsq	$"ncodeunits;", %rax
    callq	*%rax
    popq	%rcx
    retq
    nop


julia> @code_native debuginfo=:none bar([])
    .text
    movl	$1, %eax
    retq
    nopw	%cs:(%rax,%rax)

julia> @code_native debuginfo=:none foo([])
    .text
    movl	$1, %eax
    retq
    nopw	%cs:(%rax,%rax)