Comments (7)
Considered this fully explored now that all this knowledge has been concretized in #15 🙂
from alloccheck.jl.
Thank you for the detailed and thoughtful write-up! This is really excellent 👍
To write down some early thoughts:
-
Optional logging - Would it be acceptable for your use case to lift
verbose
to the type domain like this:function controller(; verbose::Val{Verbose} = Val(true)) where {Verbose} a = 0.0 for i = 1:100 a = a + i Verbose && @info "a = $a" Libc.systemsleep(0.01) end end
Trying to leave this in the value domain is possible but quite a bit more complicated - I think it'd require running inference and using that to filter which paths of the code remain live after constant propagation.
-
Check loop rather than function - There's a few options here:
- How would you feel about annotating the function call with the types you used for
check_allocs
? For exampleand then in your testsfor i = 1:N # XXX: If types change, update the `loop_body` check_allocs test loop_body(logvector::Vector{MyType}) end
@test length(check_allocs(loop_body, (Vector{MyType},))) == 0
. (This requires that you know the concrete types for that function call.) - Would it be acceptable to filter out allocation sites that might happen more than once (i.e. those that are in a loop or in a recursive function cycle? This is probably a bad idea, since the compiler has no way to tell the difference between a "small" loop in your initialization code versus a "hot" loop in your main function body.
- If neither (i) or (ii) work out, we could explore an
@inbounds
-like annotation that would only affect your function when it's run throughcheck_allocs
- How would you feel about annotating the function call with the types you used for
-
Error recovery - The current plan is to be able to check for allocations under the assumption that no errors are thrown. That means that we'd ignore any allocations in code that inevitably leads to a
throw
, and also any code that exclusively comes from a throw (i.e.catch
). Would that handle your use case correctly?
from alloccheck.jl.
Also @gbaraldi is there a way for us to ensure that check_allocs(foo, (A, B))
also covers a foo(::A, ::B)
call-site, so that the reasoning in (2i) is valid in general? Specifically, we'd want to ensure that we won't have to box normally unboxed types just to satisfy the function call ABI.
Might be a @nospecialize
corner case, but seems an important contract to provide the user.
from alloccheck.jl.
Oh and also you are right about constant propagation making the difference in (1).
What happens is that the kwcall
essentially expands to controller_inner_func_(false)
julia> @code_typed optimize=false controller()
CodeInfo(
1 ─ %1 = Main.:(var"#controller#1")(false, #self#)::Core.Const(nothing)
└── return %1
) => Nothing
which is inlined by the optimizer and then constant propagation removes the problematic code paths from there.
We can't count on that though:
- The inlining of
controller_inner_func_
is not guaranteed to happen controller()
generates different code in general thancontroller(; verbose=false)
(even if the default ofverbose
is actually false 😅)
Those caveats are why lifting to the type domain is a more reliable way to force this constant propagation.
from alloccheck.jl.
Would it be acceptable for your use case to lift verbose to the type domain like this
Yes, I don't mind, it seems like a simple and effective approach!
How would you feel about annotating the function call with the types you used for check_allocs?
Yeah that's a good idea :) Sorry for being overly pedantic, but how do I really know that for i = 1:N
doesn't allocate on me? In this case, I guess I could 1:(N::Int)
, which is good enough for me, but in general?
Would it be acceptable to filter out allocation sites that might happen more than once (i.e. those that are in a loop or in a recursive function cycle? This is probably a bad idea, since the compiler has no way to tell the difference between a "small" loop in your initialization code versus a "hot" loop in your main function body.
Maybe it could be useful to reduce noise in some situations, but I share your assessment about init loops etc.
from alloccheck.jl.
Yeah that's a good idea :) Sorry for being overly pedantic, but how do I really know that for i = 1:N doesn't allocate on me? In this case, I guess I could 1:(N::Int), which is good enough for me, but in general?
Not overly pedantic at all 🙂
The "KISS" approach would be to move the for
into loop_body
itself.
function run_almost_forever()
# Do one-time setup
N = a_large_number
logvector = zeros(N)
# Run the hot, alloc-free loop
run_almost_forever_(N::Int, logvector::Vector{Float64})
end
function run_almost_forever_(N, logvector)
for i = 1:N
y = sample_measurement()
logvector[i] = y
u = controller(y)
apply_control(u)
Libc.systemsleep(0.01)
end
end
I do get the feeling we'll want to be able to annotate regions of your code to check for allocations (it's a reasonable ask), but maybe this can tide you over until then.
from alloccheck.jl.
The "KISS" approach would be to move the for into loop_body itself.
Of course, I should have realized this one myself :) The pattern now looks very much like a "function barrier" one might use to mitigate type instability. This is already a pattern many are familiar with so that's perhaps a nice way to explain it. I like it, seems easy to understand and to work with :)
from alloccheck.jl.
Related Issues (20)
- Adjust to `Memory{T}` changes HOT 1
- Merge allocation sites per callstack
- Pipeline discrepancies + Inference instability under recursion
- Add documentation to clarify that calling an allocation-free function can still allocate
- Find a way to get source excerpts for REPL code? HOT 1
- [Feature] Warning output instead of error HOT 2
- Call-site macro HOT 3
- TagBot trigger issue HOT 3
- @check_allocs interacts badly with splatted args and kwargs
- Error from within AllocCheck when calling @check_allocs function HOT 1
- Kwargs in anonymous function
- Inconsistent with `@allocations` (Bug?) HOT 2
- Similar Package - TestNoAllocations.jl - Crossreference or Merging? HOT 6
- on Julia v1.11 non-allocating internal functions are reported as allocating HOT 3
- `@check_allocs` bypasses native Julia monomorphization limits HOT 4
- design of `@check_allocs` for static compilation
- ERROR: '@check_allocs' not documentable
- crashes Julia HOT 2
- Add semi-dynamic `ccall` support HOT 1
- Bug: ccall method definition: return type doesn't correspond to a C type HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from alloccheck.jl.