I was writing some sample code to demonstrate to coworkers how dispatching 
on types was faster than calling functions passed in as arguments, and ran 
into a performance issue I can't explain.

This is running on JuliaBox, but I observed the same behavior on a local 
machine, both with 0.3.9 and with a 2 days old master of 0.4.0. All timings 
were made after ensuring that the code was jitted.

Julia Version 0.4.0-dev+5491
Commit cb77503 (2015-06-21 09:45 UTC)


# Pass in a function
function map_f( v::Vector{Float64}, f::Function )
    ret = copy( v )
    for i in 1:length(ret)
        ret[i] = f( ret[i] )
    end
    return ret
end;

v = collect(1.0:1000000.0);
mul_two( x::Float64 ) = x * 2.0;

@time map_f( v, mul_two );

 115.911 milliseconds (4000 k allocations: 70319 KB, 5.76% gc time)


# Pass in an object associated with the function
function map_f( v::Vector{Float64}, f_type )
    ret = copy( v )
    for i in 1:length(ret)
        ret[i] = call_f( f_type, ret[i] )
    end
    return ret
end;
type MulTwo end
call_f( ::MulTwo, x::Float64 ) = x * 2.0;

@time map_f( v, MulTwo() );
4.283 milliseconds (6 allocations: 7813 KB)


So far so good. But what if I pass call_f the type itself instead of an object 
of that type?

call_f( ::Type{MulTwo}, x::Float64 ) = x * 2.0;

@time map_f( v, MulTwo );

45.678 milliseconds (2000 k allocations: 39063 KB, 14.27% gc time)


Lots more time, lots and lots more allocations. I looked at the generated code 
of map_f with @code_warntype and both versions were exactly the same except for 
the type ascribed to f_type in the Variables: section. I won't clutter this 
message with the output unless it's requested.

So I tried running julia with --track-allocation=user and finally found a 
difference:

        - function map_f( v::Vector{Float64}, f_type )
  8887450     ret = copy( v )
        0     for i in 1:length(ret)
        0         ret[i] = call_f( f_type, ret[i] )
        -     end
        0     return ret
        - end;
        - 
        - type MulTwo end
        - 
        - call_f( ::MulTwo, x::Float64 ) = x * 2.0;

vs

        - function map_f( v::Vector{Float64}, f_type )
  8887226     ret = copy( v )
        0     for i in 1:length(ret)
 16000000         ret[i] = call_f( f_type, ret[i] )
        -     end
        0     return ret
        - end;
        - 
        - type MulTwo end
        - 
 16000000 call_f( ::Type{MulTwo}, x::Float64 ) = x * 2.0;
        - 

So despite the generated code appearing to be exactly the same, somehow lots of 
allocations are going on in the version that dispatches on a type value rather 
than on a typed object.

What exactly is going on here, and is there a way that I could have figured it 
out on my own?


Reply via email to