-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added kwargs to invokelatest. #22646
Conversation
Not sure what's causing the xcode build issue. |
The first Travis OSX error is
Maybe the codegen.cpp file got corrupted somehow? Also, for the other errors, the reported line numbers seem to be off, so maybe the beginning of that file was garbled? I'll restart the build, let's see whether that helps... |
We should double-check performance here, as @JeffBezanson noted in #21543 that |
I'm not sure if these benchmark tests are cheating but: Regular ArgsBase performance:# Test Function
julia> issue19774(x::Int)::Int = 0
issue19774 (generic function with 1 method)
# 0.6.0-rc3.0
julia> @benchmark issue19774(0)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 1.503 ns (0.00% GC)
median time: 1.507 ns (0.00% GC)
mean time: 1.666 ns (0.00% GC)
maximum time: 149.130 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 1000
# PR
julia> @benchmark issue19774(0)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 1.495 ns (0.00% GC)
median time: 1.505 ns (0.00% GC)
mean time: 1.601 ns (0.00% GC)
maximum time: 51.402 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 1000 Okay, almost identical. With invokelatestfunction foo()
eval(:(issue19774(x::Int)::Int = 2))
return Base.invokelatest(issue19774, 0)
end
# 0.6.0-rc3.0
julia> @benchmark foo()
BenchmarkTools.Trial:
memory estimate: 17.85 KiB
allocs estimate: 285
--------------
minimum time: 2.261 ms (0.00% GC)
median time: 2.649 ms (0.00% GC)
mean time: 2.787 ms (0.09% GC)
maximum time: 7.857 ms (59.68% GC)
--------------
samples: 1783
evals/sample: 1
# PR
julia> @benchmark foo()
BenchmarkTools.Trial:
memory estimate: 18.95 KiB
allocs estimate: 312
--------------
minimum time: 2.362 ms (0.00% GC)
median time: 2.855 ms (0.00% GC)
mean time: 2.962 ms (0.25% GC)
maximum time: 15.707 ms (78.52% GC)
--------------
samples: 1678
evals/sample: 1 Okay, almost identical again, so it looks like the PR doesn't hurt invokelatest performance with regular args. Kwargs.# Test Function
julia> kwargs19774(x::Int, y::Int; z::Int=0)::Int = 1
kwargs19774 (generic function with 1 method)
# Base performance.
julia> @benchmark kwargs19774(2, 3; z=1)
BenchmarkTools.Trial:
memory estimate: 96 bytes
allocs estimate: 1
--------------
minimum time: 39.195 ns (0.00% GC)
median time: 41.929 ns (0.00% GC)
mean time: 56.237 ns (20.11% GC)
maximum time: 38.655 μs (99.86% GC)
--------------
samples: 10000
evals/sample: 992
# Overwriting kwargs19774
julia> function foo_kwargs()
eval(:(kwargs19774(x::Int, y::Int; z::Int=3)::Int = z))
return kwargs19774(2, 3; z=1)
end
julia> @benchmark foo_kwargs()
BenchmarkTools.Trial:
memory estimate: 18.17 KiB
allocs estimate: 258
--------------
minimum time: 1.189 ms (0.00% GC)
median time: 1.273 ms (0.00% GC)
mean time: 1.395 ms (0.35% GC)
maximum time: 18.418 ms (92.97% GC)
--------------
samples: 3533
evals/sample: 1
# With invokelatest
julia> function foo_kwargs()
eval(:(kwargs19774(x::Int, y::Int; z::Int=3)::Int = z))
return Base.invokelatest(kwargs19774, 2, 3; z=1)
end
foo_kwargs (generic function with 1 method)
julia> @benchmark foo_kwargs()
BenchmarkTools.Trial:
memory estimate: 139.40 KiB
allocs estimate: 2544
--------------
minimum time: 14.964 ms (0.00% GC)
median time: 15.985 ms (0.00% GC)
mean time: 16.381 ms (0.26% GC)
maximum time: 28.723 ms (44.39% GC)
--------------
samples: 305
evals/sample: 1 So, keyword performance isn't great, but it doesn't seem to hurt regular arg performance. |
* Replaced the `eval` with a module to help with readability. * Added a test case to demonstrate the failure condition that invokelatest fixes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are benchmarking the eval
overhead more than the actual cost of invokelatest
issue19774(x::Int)::Int = 0
kwargs19774(x::Int, y::Int; z::Int=0)::Int = 1
function newinvokelatest(f, args...; kwargs...)
inner(f, args...) = f(args...; kwargs...)
Core._apply_latest(inner, (f, args...))
end
function foo()
return Base.invokelatest(issue19774, 0)
end
function foo_new()
return newinvokelatest(issue19774, 0)
end
function foo_kwargs()
return newinvokelatest(kwargs19774, 2, 3; z=1)
end
Results
# Current implementation
julia> @benchmark foo()
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 54.827 ns (0.00% GC)
median time: 55.091 ns (0.00% GC)
mean time: 55.673 ns (0.00% GC)
maximum time: 130.690 ns (0.00% GC)
--------------
samples: 10000
evals/sample: 985
# Using inner func
julia> @benchmark foo_new()
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 3
--------------
minimum time: 830.962 ns (0.00% GC)
median time: 846.474 ns (0.00% GC)
mean time: 877.734 ns (0.82% GC)
maximum time: 21.935 μs (91.31% GC)
--------------
samples: 10000
evals/sample: 78
# Kwargs in the mix
julia> @benchmark foo_kwargs()
BenchmarkTools.Trial:
memory estimate: 592 bytes
allocs estimate: 14
--------------
minimum time: 4.265 μs (0.00% GC)
median time: 4.325 μs (0.00% GC)
mean time: 4.510 μs (2.55% GC)
maximum time: 599.276 μs (96.54% GC)
--------------
samples: 10000
evals/sample: 7
The following implementation performs better at least in my tests
function newinvokelatest2(f, args...; kwargs...)
inner() = f(args...; kwargs...)
Core._apply_latest(inner)
end
# foo_new with newinvokelatest2
@benchmark foo_new()
BenchmarkTools.Trial:
memory estimate: 112 bytes
allocs estimate: 2
--------------
minimum time: 61.380 ns (0.00% GC)
median time: 64.960 ns (0.00% GC)
mean time: 77.797 ns (12.66% GC)
maximum time: 2.548 μs (94.48% GC)
--------------
samples: 10000
evals/sample: 982
Call `Core._applu_latest` to support `open` mode in `APIResponder` on Julia 0.6. We can call `Compat.invoke_latest` instead after JuliaLang/julia#22646 is merged and assimilated in Compat.jl suitably.
Call `Core._apply_latest` to support `open` mode in `APIResponder` on Julia 0.6. We can call `Compat.invokelatest` instead after JuliaLang/julia#22646 is merged and assimilated in Compat.jl suitably.
# We use a closure (`inner`) to handle kwargs. | ||
inner() = f(args...; kwargs...) | ||
Core._apply_latest(inner) | ||
end |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, okay.
Call `Core._apply_latest` to support `open` mode in `APIResponder` on Julia 0.6. We can call `Compat.invokelatest` instead after JuliaLang/julia#22646 is merged and assimilated in Compat.jl suitably.
I'll merge this tomorrow unless there are objections. |
Closes #22642