On Sat, Apr 2, 2016 at 12:16 PM, Tim Wheeler <[email protected]> wrote:
> Thank you for the comments. In my original code it means the difference
> between a 30 min execution with memory allocation in the Gigabytes and a few
> seconds of execution with only 800 bytes using the second version.
> I thought under-the-hood Julia basically runs those if statements anyway for
> its dispatch, and don't know why it needs to allocate any memory.
> Having the if-statement workaround will be fine though.

Well, if you have a lot of these cheap functions being dynamically
dispatched I think it is not a good way to use the type. Depending on
your problem, you may be better off using a enum/flags/dict to
represent the type/get the values.

The reason for the allocation is that the return type is unknown. It
should be obvious to see if you check your code with code_warntype.

>
> On Saturday, April 2, 2016 at 7:26:11 AM UTC-7, Cedric St-Jean wrote:
>>
>>
>>> Therefore there's no way the compiler can rewrite the slow version to the
>>> fast version.
>>
>>
>> It knows that the element type is a Feature, so it could produce:
>>
>> if isa(features[i], A)
>>     retval += evaluate(features[i]::A)
>> elseif isa(features[i], B)
>>     retval += evaluate(features[i]::B)
>> else
>>     retval += evaluate(features[i])
>> end
>>
>> and it would make sense for abstract types that have few subtypes. I
>> didn't realize that dispatch was an order of magnitude slower than type
>> checking. It's easy enough to write a macro generating this expansion, too.
>>
>> On Saturday, April 2, 2016 at 2:05:20 AM UTC-4, Yichao Yu wrote:
>>>
>>> On Fri, Apr 1, 2016 at 9:56 PM, Tim Wheeler <[email protected]>
>>> wrote:
>>> > Hello Julia Users.
>>> >
>>> > I ran into a weird slowdown issue and reproduced a minimal working
>>> > example.
>>> > Maybe someone can help shed some light.
>>> >
>>> > abstract Feature
>>> >
>>> > type A <: Feature end
>>> > evaluate(f::A) = 1.0
>>> >
>>> > type B <: Feature end
>>> > evaluate(f::B) = 0.0
>>> >
>>> > function slow(features::Vector{Feature})
>>> >     retval = 0.0
>>> >     for i in 1 : length(features)
>>> >         retval += evaluate(features[i])
>>> >     end
>>> >     retval
>>> > end
>>> >
>>> > function fast(features::Vector{Feature})
>>> >     retval = 0.0
>>> >     for i in 1 : length(features)
>>> >         if isa(features[i], A)
>>> >             retval += evaluate(features[i]::A)
>>> >         else
>>> >             retval += evaluate(features[i]::B)
>>> >         end
>>> >     end
>>> >     retval
>>> > end
>>> >
>>> > using ProfileView
>>> >
>>> > features = Feature[]
>>> > for i in 1 : 10000
>>> >     push!(features, A())
>>> > end
>>> >
>>> > slow(features)
>>> > @time slow(features)
>>> > fast(features)
>>> > @time fast(features)
>>> >
>>> > The output is:
>>> >
>>> > 0.000136 seconds (10.15 k allocations: 166.417 KB)
>>> > 0.000012 seconds (5 allocations: 176 bytes)
>>> >
>>> >
>>> > This is a HUGE difference! Am I missing something big? Is there a good
>>> > way
>>> > to inspect code to figure out where I am going wrong?
>>>
>>> This is because of type instability as you will find in the performance
>>> tips.
>>> Note that slow and fast are not equivalent since the fast version only
>>> accept `A` or `B` but the slow version accepts any subtype of feature
>>> that you may ever define. Therefore there's no way the compiler can
>>> rewrite the slow version to the fast version.
>>> There are optimizations that can be applied to bring down the gap but
>>> there'll always be a large difference between the two.
>>>
>>> >
>>> >
>>> > Thank you in advance for any guidance.
>>> >
>>> >
>>> > -Tim
>>> >
>>> >
>>> >
>>> >
>>> >

Reply via email to