Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Julia - Mutable struct with Attribute which is a Function and @code_warntype

In Julia, I want to have a mutable struct with an attribute which type is a Function, this function will have arguments:

mutable struct Class_example
    function_with_arguments::Function
    some_attribute::Int
    function Class_example() new() end
    function Class_example(function_wa::Function, some_a::Int)
        this = new()
        this.function_with_arguments = function_wa
        this.some_attribute = some_a
        this
    end
end

I also want to do an action on this mutable struct :

function do_action_on_class(Class::Class_example)
    return Class.function_with_arguments(Class.some_attribute ,2.0, true)
end

Then I define a function that aims to be my class attribute :

function do_something_function(arg1::Int, arg2::Float64, arg3::Bool)
    if arg2 < 5.0
        for i in 1:arg1
            # Do Something Interesting
            @show arg3
        end
    end
    return 1
end

Finally, function_whith_arguments will be launch a huge number of time in my whole project, this is only a minimal example, so I want all this code to be very quick. That's why I use @code_warntype according to Julia's documentation Performance Tips

However, @code_warntype tells me this

body::Any
15 1 ─ %1 = (Base.getfield)(Class, :function_with_arguments)::Function
getproperty
%2 = (Base.getfield)(Class, :some_attribute)::Int64
%3 = (%1)(%2, 2.0, true)::Any                                     │ 
return %3              

Here, ::Function and the two ::Any are in red, indicating Julia can improve the performance of the code with a better implementation. So what's this correct implementation ? How should I declare my attribute function_whith_arguments as a Function type in my mutable struct ?

Whole code compilable :

mutable struct Class_example
    function_with_arguments::Function
    some_attribute::Int
    function Class_example() new() end
    function Class_example(function_wa::Function, some_a::Int)
        this = new()
        this.function_with_arguments = function_wa
        this.some_attribute = some_a
        this
    end
end


function do_action_on_class(Class::Class_example)
    return Class.function_with_arguments(Class.some_attribute ,2.0, true)
end

function do_something_function(arg1::Int, arg2::Float64, arg3::Bool)
    if arg2 < 5.0
        for i in 1:arg1
            # Do Something Interesting
            @show arg3
        end
    end
    return 1
end

function main()
    class::Class_example = Class_example(do_something_function, 4)
    @code_warntype do_action_on_class(class)
end

main()
like image 643
JKHA Avatar asked Oct 25 '18 14:10

JKHA


1 Answers

This will be efficient (well inferred). Note that I only modified (and renamed) the type.

mutable struct MyClass{F<:Function}
    function_with_arguments::F
    some_attribute::Int
end


function do_action_on_class(Class::MyClass)
    return Class.function_with_arguments(Class.some_attribute ,2.0, true)
end

function do_something_function(arg1::Int, arg2::Float64, arg3::Bool)
    if arg2 < 5.0
        for i in 1:arg1
            # Do Something Interesting
            @show arg3
        end
    end
    return 1
end

function main()
    class::MyClass = MyClass(do_something_function, 4)
    @code_warntype do_action_on_class(class)
end

main()

What did I do?

  • If you care about performance, you should never have fields of an abstract type, and isabstracttype(Function) == true. What you should do instead is parameterize on that fields type (F above, which can be any function. Note that isconcretetype(typeof(sin)) == true). This way, for any particular instance of MyCall the precise concrete type of every field is known at compile time.

  • Irrelevant for performance but: There is no need for a constructor that simply assigns all the arguments to all the fields. Such a constructor is defined by default implicitly.

You can read more about parametric types here.

On a side note, what you are doing looks a lot like trying to write OO-style in Julia. I'd recommend to not do this but instead use Julia the Julia way using multiple dispatch.

like image 149
carstenbauer Avatar answered Nov 06 '22 17:11

carstenbauer