My mental model of how Julia handles dynamic dispatch is by compiling code that looks like:
for ele in vector_of_abstract_type do_something(ele) end
to something that looks like this
for ele in vector_of_abstract_type type_of_ele = ele.type # inspect some type information stored with the object if type_of_ele in do_something_method_table do_something_method_specialization = do_something_method_table[type_of_ele] # lookup method specialization do_something_method_specialization(ele) # jump to already compiled code machine code else do_something_function_specialization = determine_method(do_something, type_of_ele) # determine which method of the do_something_function to use do_something_method_specialization = compile(do_something_function_specialization, type_of_ele) # compile a specialization of the method based on type_of_ele do_something_method_table[type_of_ele] = do_something_method_specialization # store a reference to the final compiled machine code do_something_method_specialization(ele) # jump to the machine code end end
Basically the cost of dynamic dispatch according to this is the same as a single hash table lookup if the specialization has already been compiled. Else it requires determining the method to use and then compiling that method for our specific type.
I differentiate method speicalizations (raw machine code) from function specializations (which method of a function is called).
1.) Is this what actually happens? Does the hash table map the types of the inputs to the method (function specialization) or the final compiled machine code location (method speicalization)? In my understanding above it was the latter.
2.) If do_something is roughly 10x the cost of a dictionary lookup then can we ignore the cost of dynamic dispatch (given that the type instability doesn’t propagate to other parts of the code)? Is dictionary lookup a good baseline cost comparison?
3.) If we know that the return type of do_something is the same, no matter what the type of ele is, can we assume that the compiler also knows? Can we use a return value in this case in a type stable way? Or should we put a type assertion? If we put a type assertion will the compiler be able to continue in a type stable way with that return value?
4.) If do_something adds a new method to do_something then will the hash table be emptied out? Thus causing the else branch to execute every time leading to very slow code?
5.) Kind of unrelated, but does the compiler compile as deep as it can until it hits a value with a type it cannot determine at compile time, insert a callback to itself there, unwind and execute? Or does it just compile the local function and at any function call point insert a callback to itself, followed by an overwriting of that callback once it compiles the callee?
6.) Does Julia store some type identifying information with every piece of data?