Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,17 @@ end
isempty(itr) = done(itr, start(itr))

"""
invokelatest(f, args...)
invokelatest(f, args...; kwargs...)

Calls `f(args...)`, but guarantees that the most recent method of `f`
Calls `f(args...; kwargs...)`, but guarantees that the most recent method of `f`
will be executed. This is useful in specialized circumstances,
e.g. long-running event loops or callback functions that may
call obsolete versions of a function `f`.
(The drawback is that `invokelatest` is somewhat slower than calling
`f` directly, and the type of the result cannot be inferred by the compiler.)
"""
invokelatest(f, args...) = Core._apply_latest(f, args)
function invokelatest(f, args...; kwargs...)
# We use a closure (`inner`) to handle kwargs.
inner() = f(args...; kwargs...)
Core._apply_latest(inner)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to me to construct a new closure for each call. Wouldn't it be better to do:

_invoker(f, args, kwargs) = f(args..., kwargs...)
invokelatest(f, args...; kwargs...) = Core._apply_latest(_invoker, args, kwargs)

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't you need to do Core._apply_latest(_invoker, (f, args, kwargs))? In that case, the no-kwargs conditions would be slower.

julia> using BenchmarkTools

julia> issue19774(x::Int)::Int = 0
issue19774 (generic function with 1 method)

julia> kwargs19774(x::Int, y::Int; z::Int=0)::Int = 1
kwargs19774 (generic function with 1 method)

julia> function newinvokelatest(f, args...; kwargs...)
           inner() = f(args...; kwargs...)
           Core._apply_latest(inner)
       end
newinvokelatest (generic function with 1 method)

julia> newinvokelatest2(f, args...; kwargs...) = Core._apply_latest(_newinvoker, (f, args, kwargs))
newinvokelatest2 (generic function with 1 method)

julia> _newinvoker(f, args, kwargs) = f(args...; kwargs...)
_newinvoker (generic function with 1 method)

julia> function foo()
           return Base.invokelatest(issue19774, 0)
       end
foo (generic function with 1 method)

julia> function foo_new()
           return newinvokelatest(issue19774, 0)
       end
foo_new (generic function with 1 method)

julia> function foo_new2()
           return newinvokelatest2(issue19774, 0)
       end
foo_new2 (generic function with 1 method)

julia> function foo_kwargs()
           return newinvokelatest(kwargs19774, 2, 3; z=1)
       end
foo_kwargs (generic function with 1 method)

julia> function foo_kwargs2()
           return newinvokelatest2(kwargs19774, 2, 3; z=1)
       end
foo_kwargs2 (generic function with 1 method)

julia> @benchmark foo()
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     55.914 ns (0.00% GC)
  median time:      56.209 ns (0.00% GC)
  mean time:        59.357 ns (0.00% GC)
  maximum time:     193.226 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     984

julia> @benchmark foo_new()
BenchmarkTools.Trial:
  memory estimate:  112 bytes
  allocs estimate:  2
  --------------
  minimum time:     69.090 ns (0.00% GC)
  median time:      71.658 ns (0.00% GC)
  mean time:        84.819 ns (9.79% GC)
  maximum time:     2.234 μs (93.51% GC)
  --------------
  samples:          10000
  evals/sample:     976

julia> @benchmark foo_new2()
BenchmarkTools.Trial:
  memory estimate:  128 bytes
  allocs estimate:  3
  --------------
  minimum time:     231.818 ns (0.00% GC)
  median time:      235.975 ns (0.00% GC)
  mean time:        266.367 ns (4.67% GC)
  maximum time:     9.024 μs (92.26% GC)
  --------------
  samples:          10000
  evals/sample:     451

julia> @benchmark foo_kwargs()
BenchmarkTools.Trial:
  memory estimate:  1008 bytes
  allocs estimate:  20
  --------------
  minimum time:     3.734 μs (0.00% GC)
  median time:      3.801 μs (0.00% GC)
  mean time:        4.224 μs (3.51% GC)
  maximum time:     638.504 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     8

julia> @benchmark foo_kwargs2()
BenchmarkTools.Trial:
  memory estimate:  560 bytes
  allocs estimate:  12
  --------------
  minimum time:     3.399 μs (0.00% GC)
  median time:      3.485 μs (0.00% GC)
  mean time:        3.902 μs (2.53% GC)
  maximum time:     517.470 μs (95.49% GC)
  --------------
  samples:          10000
  evals/sample:     8

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, okay.

39 changes: 34 additions & 5 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -722,13 +722,42 @@ if Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0"))))
end
end

# invokelatest function for issue #19774
issue19774(x) = 1
# Test issue #19774 invokelatest fix.

# we define this in a module to allow rewriting
# rather than needing an extra eval.
module Issue19774
f(x) = 1
end

# First test the world issue condition.
let foo() = begin
Issue19774.f(x::Int) = 2
return Issue19774.f(0)
end
@test foo() == 1 # We should be using the original function.
end

# Now check that invokelatest fixes that issue.
let foo() = begin
Issue19774.f(x::Int) = 3
return Base.invokelatest(Issue19774.f, 0)
end
@test foo() == 3
end

# Check that the kwargs conditions also works
module Kwargs19774
f(x, y; z=0) = x * y + z
end

@test Kwargs19774.f(2, 3; z=1) == 7

let foo() = begin
eval(:(issue19774(x::Int) = 2))
return Base.invokelatest(issue19774, 0)
Kwargs19774.f(x::Int, y::Int; z=3) = z
return Base.invokelatest(Kwargs19774.f, 2, 3; z=1)
end
@test foo() == 2
@test foo() == 1
end

# Endian tests
Expand Down