Re: [julia-users] Re: Debugging stack allocation
On Thu, Sep 22, 2016 at 8:44 AM, Jamie Brandonwrote: > 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
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 Brandonwrote: > 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
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 Brandonwrote: > 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? >