Giter Club home page Giter Club logo

Comments (7)

topolarity avatar topolarity commented on May 29, 2024 1

Considered this fully explored now that all this knowledge has been concretized in #15 🙂

from alloccheck.jl.

topolarity avatar topolarity commented on May 29, 2024

Thank you for the detailed and thoughtful write-up! This is really excellent 👍

To write down some early thoughts:

  1. 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.

  2. Check loop rather than function - There's a few options here:

    1. How would you feel about annotating the function call with the types you used for check_allocs? For example
      for i = 1:N
          # XXX: If types change, update the `loop_body` check_allocs test
          loop_body(logvector::Vector{MyType})
      end
      and then in your tests @test length(check_allocs(loop_body, (Vector{MyType},))) == 0. (This requires that you know the concrete types for that function call.)
    2. 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.
    3. If neither (i) or (ii) work out, we could explore an @inbounds-like annotation that would only affect your function when it's run through check_allocs
  3. 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.

topolarity avatar topolarity commented on May 29, 2024

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.

topolarity avatar topolarity commented on May 29, 2024

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:

  1. The inlining of controller_inner_func_ is not guaranteed to happen
  2. controller() generates different code in general than controller(; verbose=false)
    (even if the default of verbose 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.

baggepinnen avatar baggepinnen commented on May 29, 2024

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.

topolarity avatar topolarity commented on May 29, 2024

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.

baggepinnen avatar baggepinnen commented on May 29, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.