Cost of checking if statements in type parameters in a big loop and generated functions


#1

I am curious as to how Julia’s compiler handles if statements in type parameters. Does it select one specific branch based on the arguments which will always be true as the type parameter is constant, or does it keep checking the if statement in every loop iteration?

And if the latter is true, would this be a good use case of using generated functions to generate a slightly different function for each type parameter as opposed to using if statements to specialize the behaviour inside a big loop?


#2

Can you give an example? I’m not really sure what sort of application you are thinking about.


#3

I think this is the question:

julia> f(a) = isa(a,AbstractArray) ? 1 : 3.0                                                                                           
f (generic function with 1 method)                                                                                                     

julia> @code_warntype f([1])                                                                                                           
Variables:                                                                                                                             
  #self#::#f                                                                                                                           
  a::Any                                                                                                                               

Body:
  begin                                                                                                                                
      return 1                                                                                                                         
  end::Int64                                                                                                                           

julia> @code_native f([1])                                                                                                             
        .text                                                                                                                          
Filename: REPL[1]
        pushq   %rbp
        movq    %rsp, %rbp
Source line: 1
        movl    $1, %eax
        popq    %rbp
        retq
        nopl    (%rax,%rax)

(in Julia 0.6). This shows that the branch is eliminated.

Note as Julia gives you great inspection tools, such as @code_*, you can often answer such questions yourself with a bit of digging.


#4

Type parameters are known at compile time, and the if statements are eliminated at compile time as @mauro3 showed. Feel free to use them without runtime performance issues.


#5

So for a more sophisticated if statement that depends on the input but does not change throughout the course of the loop, would this be a use case for generated functions? I am still trying to get my head around when a generated function would be favourable over a normal function and why.


#6

Generated functions only know the type, not the input value. So that will not help you there. Anyways, branch prediction will likely handle this just fine, so you shouldn’t have to special-case it yourself.

Generated functions are useful when a function is supposed to heavily specialize on the given types, but it would take a lot of work to write down every combination of types that come in. Here’s an example:

Each SVector has its length as part of its type information (as a type parameter). To make matrix multiplication fast for small SArrays, it’s wise to unroll the loop. You can by hand write down matrix multiplication for size 1 SArray, size 2 SArray, etc., but that would take a lot of work and you’d never get every case. Instead, @generated is used here to generate “what you would have wanted to write by hand”, the expression without loops or any of that. Again, this is done because the size is part of the type information, and you are effectively generating a bunch of method definitions.


#7

I see, thanks for the detailed explanation. Btw what is exactly the purpose of this bit of code below and where is T defined? If I understand correctly, if sa[2] is 0, then a is essentially a column vector, and multiplication only makes sense if b was a scalar, a 1 element vector or a 1x1 matrix to cover all the cases. So how come it is evaluating to a zero vector, and probably T will give an error?


#8

No idea, not my code so I’d have to play around with it a bit. I just pulled StaticArrays out of the hat because I knew they unrolled a bunch so finding an example would be easy there.


#9

Fair enough, thanks a lot.