How to make this Type-stable: Read a list of operation from a user file

How to make this Type-stable:

I will read a list of operation from a user file, which defines what operation I want to do (e.g. Run, Shout, Cut, Roll etc)

Each operation will have their own “struct” or “behavior” that I defined.

Then after reading all operation into an Array or Tuple, I have initialise my variables, and next I will iterate all operation to let them run.

The problem is that I cannot make this Array of operation a Concrete Type, since the element are different. This make my code type-unstable.

Running 1 operation take around 1 minute
But running 100 operation take > 1 * 100 minutes, more like 150-200 minute

This is an example below, when i do code_warntype, there are many Red alert.
How can I write type-stable code, If I need to perform an Array of Operations that is defined by another use file, and each operation can contain same member but also different member types?

Thank you

abstract type Operate end

struct Cut <: Operate    
    member::String
end
Cut() = Cut("t")

struct Push <: Operate
    member::Int
end
Push() = Push(1)

struct Roll <: Operate
    member::Symbol
end
Roll() = Roll(:a)

struct ManyOps{T}
    ops::T
end
ManyOps(ops::Operate...) = ManyOps(ops)

o = ManyOps(Cut(), Roll(), Cut(), Push(), Push())

function run_program(o)
    for i in o.ops
        println(i.member)
    end
end

run_program(o)
@code_warntype run_program(o)

How about something like this:

struct Operate2{T}     
    o::T
end

struct Operate4 
    o::T where T
end 

v2 = [Operate2(Roll()), Operate2(Push()), Operate2(Cut())]
v4 = [Operate4(Push()), Operate4(Roll()), Operate4(Cut())]

This seems like an ideal case for a function barrier. If your code looks something like this:

for op in operations
  run_operation(op)
end

where operations::Vector{Operate} or ::Vector{Any} and run_operation takes more than a few microseconds, then the performance penalty of the type-unstable loop over operations should not matter at all.

There will be a cost to look up the correct run_operation() method for each element of operations, but that cost is usually on the order of 100 nanoseconds. So if run_operation takes 1 minute, then calling it on a type-unstable input should take 1 minute + 100 nanonseconds. The key is to make sure that you put all of the complicated work of running the operation inside the run_operation function, rather than putting it directly into the loop.

3 Likes

What if I need to know the iteration index within the for loop?
e.g. will below this be considered as type-stable?

for i in 1:length(operations) run_operation(op[i]) end

I would recommend enumerate if you need to know the index (for anything else but looking up the element, because for that iteration will be fine).

This alone should not affect type stability or the function barrier technique.

1 Like