Re: [julia-users] Re: Debugging stack allocation

2016-09-22 Thread Yichao Yu
On Thu, Sep 22, 2016 at 8:44 AM, Jamie Brandon
 wrote:
> Well, I managed to answer the first part of my question. A variable won't be
> stack-allocated unless the compiler can prove it is always defined before
> being used. Mine were, but the control flow was too complex for the compiler
> to deal with. I added `local finger = Finger{1}(0,0)` in a few places to
> make life easier for the compiler and my allocations went away.
>
> I'm still interested in the second half of the question though - is there a
> better way to debug allocation problems other than guesswork?

For most cases code_warntype should be able to tell you that. The
use-before-assign is one of the few cases that's not captured by it.
(Other cases are mostly due to the compiler heuristics specializing on
types/functions). Ideally, use-before-assign issue should be fixed and
avoid having to box the variable.

>
> On 22 September 2016 at 14:06, Jamie Brandon 
> wrote:
>>
>> Oh, for comparison, this simper function contains no heap allocation at
>> all, so the compiler is definitely willing to do this under some
>> circumstances.
>>
>> function foo()
>>   finger = Finger{1}(1,1)
>>   for _ in 1:1000
>> finger = Finger{1}(finger.hi + finger.lo, finger.lo)
>>   end
>>   finger
>> end
>>
>> On 22 September 2016 at 14:01, Jamie Brandon
>>  wrote:
>>>
>>> I have a query compiler which emits Julia code. The code contains lots of
>>> calls to generated functions, which include sections like this:
>>>
>>> hi = gallop(column, column[finger.lo], finger.lo, finger.hi, <=)
>>> Finger{$(C+1)}(finger.lo, hi)
>>>
>>> Finger is an immutable, isbits type:
>>>
>>>   immutable Finger{C}
>>> lo::Int64
>>> hi::Int64
>>>   end
>>>
>>> When I run the generated code I see many millions of allocations. Using
>>> code_warntype I can see that all the generated functions have been inlined,
>>> every variable has a concrete inferred type and there are no generic calls.
>>> And yet I see many sections in the llvm code like this:
>>>
>>>   %235 = call %jl_value_t* @jl_gc_pool_alloc(i8* %ptls_i8, i32 1456, i32
>>> 32)
>>>   %236 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 -1,
>>> i32 0
>>>   store %jl_value_t* inttoptr (i64 139661604385936 to %jl_value_t*),
>>> %jl_value_t** %236, align 8
>>>   %237 = bitcast %jl_value_t* %235 to i64*
>>>   store i64 %229, i64* %237, align 8
>>>   %238 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 1
>>>   %239 = bitcast %jl_value_t* %238 to i64*
>>>   store i64 %234, i64* %239, align 8
>>>   store %jl_value_t* %235, %jl_value_t** %finger_2_2, align 8
>>>   %.pr = load %jl_value_t*, %jl_value_t** %finger_1_2, align 8
>>>
>>> The pointer on the third line is:
>>>
>>>   unsafe_pointer_to_objref(convert(Ptr{Any}, 139661604385936))
>>>   # => Data.Finger{1}
>>>
>>> So it appears that these fingers are still being heap-allocated.
>>>
>>> What could cause this? And more generally, how does one debug issues like
>>> this? Is there any way to introspect on the decision?
>>
>>
>


[julia-users] Re: Debugging stack allocation

2016-09-22 Thread Jamie Brandon
Well, I managed to answer the first part of my question. A variable won't
be stack-allocated unless the compiler can prove it is always defined
before being used. Mine were, but the control flow was too complex for the
compiler to deal with. I added `local finger = Finger{1}(0,0)` in a few
places to make life easier for the compiler and my allocations went away.

I'm still interested in the second half of the question though - is there a
better way to debug allocation problems other than guesswork?

On 22 September 2016 at 14:06, Jamie Brandon 
wrote:

> Oh, for comparison, this simper function contains no heap allocation at
> all, so the compiler is definitely willing to do this under some
> circumstances.
>
> function foo()
>   finger = Finger{1}(1,1)
>   for _ in 1:1000
> finger = Finger{1}(finger.hi + finger.lo, finger.lo)
>   end
>   finger
> end
>
> On 22 September 2016 at 14:01, Jamie Brandon  > wrote:
>
>> I have a query compiler which emits Julia code. The code contains lots of
>> calls to generated functions, which include sections like this:
>>
>> hi = gallop(column, column[finger.lo], finger.lo, finger.hi, <=)
>> Finger{$(C+1)}(finger.lo, hi)
>>
>> Finger is an immutable, isbits type:
>>
>>   immutable Finger{C}
>> lo::Int64
>> hi::Int64
>>   end
>>
>> When I run the generated code I see many millions of allocations. Using
>> code_warntype I can see that all the generated functions have been inlined,
>> every variable has a concrete inferred type and there are no generic calls.
>> And yet I see many sections in the llvm code like this:
>>
>>   %235 = call %jl_value_t* @jl_gc_pool_alloc(i8* %ptls_i8, i32 1456, i32
>> 32)
>>   %236 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 -1,
>> i32 0
>>   store %jl_value_t* inttoptr (i64 139661604385936 to %jl_value_t*),
>> %jl_value_t** %236, align 8
>>   %237 = bitcast %jl_value_t* %235 to i64*
>>   store i64 %229, i64* %237, align 8
>>   %238 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 1
>>   %239 = bitcast %jl_value_t* %238 to i64*
>>   store i64 %234, i64* %239, align 8
>>   store %jl_value_t* %235, %jl_value_t** %finger_2_2, align 8
>>   %.pr = load %jl_value_t*, %jl_value_t** %finger_1_2, align 8
>>
>> The pointer on the third line is:
>>
>>   unsafe_pointer_to_objref(convert(Ptr{Any}, 139661604385936))
>>   # => Data.Finger{1}
>>
>> So it appears that these fingers are still being heap-allocated.
>>
>> What could cause this? And more generally, how does one debug issues like
>> this? Is there any way to introspect on the decision?
>>
>
>


[julia-users] Re: Debugging stack allocation

2016-09-22 Thread Jamie Brandon
Oh, for comparison, this simper function contains no heap allocation at
all, so the compiler is definitely willing to do this under some
circumstances.

function foo()
  finger = Finger{1}(1,1)
  for _ in 1:1000
finger = Finger{1}(finger.hi + finger.lo, finger.lo)
  end
  finger
end

On 22 September 2016 at 14:01, Jamie Brandon 
wrote:

> I have a query compiler which emits Julia code. The code contains lots of
> calls to generated functions, which include sections like this:
>
> hi = gallop(column, column[finger.lo], finger.lo, finger.hi, <=)
> Finger{$(C+1)}(finger.lo, hi)
>
> Finger is an immutable, isbits type:
>
>   immutable Finger{C}
> lo::Int64
> hi::Int64
>   end
>
> When I run the generated code I see many millions of allocations. Using
> code_warntype I can see that all the generated functions have been inlined,
> every variable has a concrete inferred type and there are no generic calls.
> And yet I see many sections in the llvm code like this:
>
>   %235 = call %jl_value_t* @jl_gc_pool_alloc(i8* %ptls_i8, i32 1456, i32
> 32)
>   %236 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 -1,
> i32 0
>   store %jl_value_t* inttoptr (i64 139661604385936 to %jl_value_t*),
> %jl_value_t** %236, align 8
>   %237 = bitcast %jl_value_t* %235 to i64*
>   store i64 %229, i64* %237, align 8
>   %238 = getelementptr inbounds %jl_value_t, %jl_value_t* %235, i64 1
>   %239 = bitcast %jl_value_t* %238 to i64*
>   store i64 %234, i64* %239, align 8
>   store %jl_value_t* %235, %jl_value_t** %finger_2_2, align 8
>   %.pr = load %jl_value_t*, %jl_value_t** %finger_1_2, align 8
>
> The pointer on the third line is:
>
>   unsafe_pointer_to_objref(convert(Ptr{Any}, 139661604385936))
>   # => Data.Finger{1}
>
> So it appears that these fingers are still being heap-allocated.
>
> What could cause this? And more generally, how does one debug issues like
> this? Is there any way to introspect on the decision?
>