Skip to content
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

use our own struct for the local method table instead of TypeMapEntry #313

Merged
merged 3 commits into from
Aug 16, 2019

Conversation

KristofferC
Copy link
Member

No performance difference but I feel this should be easier to maintain.

Slightly lifted from #309

@codecov-io
Copy link

codecov-io commented Jul 31, 2019

Codecov Report

Merging #313 into master will increase coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #313      +/-   ##
==========================================
+ Coverage   88.17%   88.18%   +0.01%     
==========================================
  Files          11       11              
  Lines        1742     1744       +2     
==========================================
+ Hits         1536     1538       +2     
  Misses        206      206
Impacted Files Coverage Δ
src/JuliaInterpreter.jl 96% <ø> (ø) ⬆️
src/construct.jl 92.4% <100%> (ø) ⬆️
src/localmethtable.jl 100% <100%> (ø) ⬆️
src/types.jl 71.71% <100%> (+0.28%) ⬆️
src/optimize.jl 96.38% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 957625b...4a704d2. Read the comment docs.

mutable struct DispatchableMethod
next::Union{Nothing,DispatchableMethod} # linked-list representation
frameinstance::Any # really a FrameInstance but we have a cyclic dependency
sig::Type # for speed of matching, this is a *concrete* signature. `sig <: frameinstance.framecode.scope.sig`
Copy link
Member

Choose a reason for hiding this comment

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

If you're doing this, can you implement the compiled idea too? With this diff

tim@diva:~/.julia/dev/JuliaInterpreter$ git diff
diff --git a/src/construct.jl b/src/construct.jl
index 9b70786..796c4ab 100644
--- a/src/construct.jl
+++ b/src/construct.jl
@@ -236,6 +236,7 @@ function prepare_call(@nospecialize(f), allargs; enter_generated = false)
     end
     argtypesv = Any[_Typeof(a) for a in allargs]
     argtypes = Tuple{argtypesv...}
+    println("Calling which (super-slow)")
     method = whichtt(argtypes)
     if method === nothing
         # Call it to generate the exact error

and the following test code

using JuliaInterpreter

bar(::Int) = 1
bar(::Float64) = 2
push!(JuliaInterpreter.compiled_methods, @which bar(1.0))

function foo(A)
    s = 0
    for a in A
        @bp
        s += bar(a)
    end
    return s
end
A = Real[1, 1, 1, 1, 1.0, 1.0, 1.0, 1.0]
frame = JuliaInterpreter.enter_call(foo, A)
for i = 1:length(A)
    println("iteration ", i)
    debug_command(frame, :c)
    for i = 1:3
        debug_command(frame, :se)  # step forward to the call to bar
    end
    # display(frame.framecode.methodtables)
end

I think you'll see what I'm worried about. (Note that the Int method of bar is recursively interpreted and the Float64 one in Compiled mode.) If you inspect frame.framecode.methodtables you'll see that Compiled methods can't exploit the local method tables.

In the longer run we want to store the MethodInstance, and then use a ccall to skip the jl_lookup_generic from jl_apply_generic. That would basically mean DLLEXPORTing this. That will eliminate the runtime dispatch on compiled dispatch from the interpreter.

Copy link
Member

Choose a reason for hiding this comment

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

Correction: there's already this, so we don't have to modify Julia at all.

Copy link
Member Author

@KristofferC KristofferC Jul 31, 2019

Choose a reason for hiding this comment

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

I added caching for Compiled calls as well.

Copy link
Member Author

@KristofferC KristofferC Jul 31, 2019

Choose a reason for hiding this comment

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

function jl_generic(args)
    args[1] + args[2]
end

function jl_invoke(args, mi)
     ccall(:jl_invoke, Any, (Any, Ptr{Any}, Cuint, Any), +, args, 2, mi)
end

args = Any[1, 2]
@btime jl_generic($args)
# 25 ns
mi = Core.Compiler.specialize_method(@which(1 + 1), Tuple{typeof(+), Int, Int}, Core.svec())
@btime  jl_invoke($args, $mi)
# 11 ns

Copy link
Member

Choose a reason for hiding this comment

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

Not quite sure I get what foo is, but if you're testing jl_invoke against run-time dispatch keep in mind that the timings are rather dependent on how many methods there are. convert might be in a different category 😄.

Copy link
Member Author

@KristofferC KristofferC Jul 31, 2019

Choose a reason for hiding this comment

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

Sorry, had a typo, foo should have been jl_invoke. Just wanted to show that using jl_invoke with a MethodInstance was indeed faster :)

mutable struct DispatchableMethod
next::Union{Nothing,DispatchableMethod} # linked-list representation
frameinstance::Any # really a FrameInstance but we have a cyclic dependency
sig::Type # for speed of matching, this is a *concrete* signature. `sig <: frameinstance.framecode.scope.sig`
Copy link
Member

Choose a reason for hiding this comment

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

Not quite sure I get what foo is, but if you're testing jl_invoke against run-time dispatch keep in mind that the timings are rather dependent on how many methods there are. convert might be in a different category 😄.

@@ -68,7 +75,7 @@ Important fields:
struct FrameCode
scope::Union{Method,Module}
src::CodeInfo
methodtables::Vector{Union{Compiled,TypeMapEntry}} # line-by-line method tables for generic-function :call Exprs
methodtables::Vector{Union{Compiled,DispatchableMethod}} # line-by-line method tables for generic-function :call Exprs
Copy link
Member

Choose a reason for hiding this comment

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

I don't see how this prevents which-based lookup on each iteration of a loop. In #309 I added a compiled field to DispatchableMethod, which seems like the right place for this information (once you've determined that a method matches, your next question is whether you should run it in interpreted or compiled mode).

Copy link
Member Author

Choose a reason for hiding this comment

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

It prevents it because frameinstance in DispatchableMethodis a Compiled in those cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason I still want to have Compiled directly in methodtables is for ccall / fcall where we know for sure that the method we want to call are compiled so there is no need to do any matching against signatures.

abstract type AbstractFrameInstance end
mutable struct DispatchableMethod
next::Union{Nothing,DispatchableMethod} # linked-list representation
frameinstance::Union{Compiled, AbstractFrameInstance} # really a Union{Compiled, FrameInstance} but we have a cyclic dependency
Copy link
Member

Choose a reason for hiding this comment

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

This could hurt. Union-splitting may not work, unless the compiler is smart enough to know there is only one concrete subtype.

Copy link
Member Author

@KristofferC KristofferC Jul 31, 2019

Choose a reason for hiding this comment

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

Yeah but we type assert immediately when we use it anyway but I will time some stuff and see if I can do it some other way.

Where the stuff in TypeMapEntry inferrable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looked a bit, there is a single place where we look at this field and all uses either just calls isa on it, or type asserts before it is used so there should be no dynamic behavior.

@KristofferC KristofferC reopened this Aug 16, 2019
@KristofferC KristofferC merged commit dd56353 into master Aug 16, 2019
@timholy
Copy link
Member

timholy commented Aug 17, 2019

Thanks for doing this and all the other PRs. I've been out on vacation (still am, actually), it's great to have the package in such good hands!

@KristofferC KristofferC deleted the kc/own_type branch August 17, 2019 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants