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

Specialization despite nospecialize annotation #35131

Closed
timholy opened this issue Mar 16, 2020 · 22 comments · Fixed by #41931
Closed

Specialization despite nospecialize annotation #35131

timholy opened this issue Mar 16, 2020 · 22 comments · Fixed by #41931
Assignees

Comments

@timholy
Copy link
Sponsor Member

timholy commented Mar 16, 2020

Using nspecializations from here, I find the following behavior strange:

julia> function abstractparameters(T::Type, AT::Type)
           @nospecialize T AT
           @assert T <: AT
           Tst = supertype(T)
           while Tst <: AT
               T = Tst
               Tst = supertype(T)
           end
           return T.parameters
       end
abstractparameters (generic function with 1 method)

julia> abstractparameters(typeof(rand(3)), AbstractArray)
svec(Float64, 1)

julia> abstractparameters(typeof(rand(Float32, 3)), AbstractArray)
svec(Float32, 1)

julia> abstractparameters(typeof(rand(Int, 3)), AbstractArray)
svec(Int64, 1)

julia> m = first(methods(abstractparameters))
abstractparameters(T::Type, AT::Type) in Main at REPL[3]:2

julia> nspecializations(m)   # this is what I expect
1

julia> @generated function f(a::AbstractArray)
           T, N = abstractparameters(a, AbstractArray)    # call it from a @generated method
           :($T(1))
       end
f (generic function with 1 method)

julia> f(rand(3))
1.0

julia> f(rand(Float32, 3))
1.0f0

julia> nspecializations(m)  # this is what I don't want
2

julia> m.specializations
Core.TypeMapEntry(Core.TypeMapEntry(nothing, Tuple{typeof(abstractparameters),Type,Type}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type, ::Type), false, false, false), Tuple{typeof(abstractparameters),Type,Type{AbstractArray}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type, ::Type{AbstractArray}), false, false, false)

This is a MWE of a bigger problem: in LoopVectorization, I've measured at least 33 specializations of abstractparameters despite the fact that both arguments are marked as not being specialiizable. In that case there are specializations on both parameters. Here's 30 of them:

julia> m.specializations.arg1[1].targ
30-element Array{Any,1}:
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{1,3}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{1,3}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)  
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float64,Tuple{1,3}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float64,Tuple{1,3}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)  
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{1,73}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{1,73}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{1,75}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{1,75}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)      
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{69,1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{69,1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{73,1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{73,1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{73}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{73}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)    
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float32,Tuple{75,1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float32,Tuple{75,1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Float64,Tuple{1,73}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Float64,Tuple{1,73}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)
                                                                                                                                                                                                                                                                                                                                                                                                                   
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int32,Tuple{73}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int32,Tuple{73}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)        
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int32,Tuple{75,1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int32,Tuple{75,1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)    
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int64,Tuple{1,73}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int64,Tuple{1,73}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)    
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int64,Tuple{1,75}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int64,Tuple{1,75}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)    
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int64,Tuple{1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int64,Tuple{1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)          
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int64,Tuple{69,1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int64,Tuple{69,1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)    
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int64,Tuple{73,1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int64,Tuple{73,1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)    
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int64,Tuple{73}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int64,Tuple{73}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)        
 Core.TypeMapEntry(nothing, Tuple{typeof(LoopVectorization.abstractparameters),Type{StaticStridedPointer{Int64,Tuple{75,1}}},Type{VectorizationBase.AbstractStaticStridedPointer}}, nothing, svec(), 0x0000000000000001, 0xffffffffffffffff, MethodInstance for abstractparameters(::Type{StaticStridedPointer{Int64,Tuple{75,1}}}, ::Type{VectorizationBase.AbstractStaticStridedPointer}), false, true, false)
@vtjnash
Copy link
Sponsor Member

vtjnash commented Mar 16, 2020

FWIW, .specializations has nothing to do with @nospecialize

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 16, 2020

Care to explain? Or a link? I have a vague memory of this but I can't recall the particulars.

Is there something else I should be using to measure the number of compiled specializations for each method?

@JeffBezanson
Copy link
Sponsor Member

The specializations in that table have been inferred, but not compiled. We could add some kind of @noinfer maybe, which would be much more drastic.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 16, 2020

I see. Presumably this would have quite a few use-cases. I would guess there's hardly ever a reason to infer calls in a @generated function's generator, since it will only run once.

UDPATE: you can combine @nospecialize with a Base.inferencebarrier(arg) in the caller. That will prevent inference-specialization of the callee on that argument (if that argument is @nospecialized in the callee). This seems like a very reasonable state of affairs.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 16, 2020

For packages like LoopVectorization and Cassette which do a lot with generated functions, it seems this might decrease latency a fair bit?

@JeffBezanson
Copy link
Sponsor Member

I don't believe we spend a lot of time compiling and running generators, especially since they run in fixed worlds and we add @nospecialize on their arguments. Since generators are only invoked internally and not from other user code, that has the effect of inferring them only once each. Probably worth a quick experiment though.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 16, 2020

In case it helps I pushed a PR you can use as a real-world test case: JuliaSIMD/LoopVectorization.jl#76

@JeffBezanson
Copy link
Sponsor Member

I tried running the LoopVectorization tests with ENABLE_TIMINGS, but with all the compiler timers disabled so that the time would be divided between ROOT and STAGED_FUNCTION. Staged is about 5%. The full profile is a bit unusual though:

INFERENCE                 : 14.35 %   116815484879
CODEGEN                   : 29.95 %   243877289111
METHOD_LOOKUP_SLOW        :  0.03 %   266521094
METHOD_LOOKUP_FAST        :  2.23 %   18125841661
LLVM_OPT                  : 41.62 %   338942587471

It's rare to see that much time in codegen.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 17, 2020

Does the 5% STAGED_FUNCTION include things triggered by running the generator, but which are not @generated themselves?

Interesting, though, that's it's only 15% inference.

@JeffBezanson
Copy link
Sponsor Member

Yes, it includes everything that runs while doing code generation.

That also brings up why it's a bit difficult to use different compilation parameters for generators --- a generator might call any function, and we probably do want to infer most/all of those, so it's hard to know where to draw the line.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 17, 2020

I see what you mean. I'd guess that most of the code in LoopVectorization itself could happily run in the interpreter, but lots of the Base methods they call are generically useful and would probably be better run in compiled mode.

@JeffBezanson
Copy link
Sponsor Member

This would be a great use case for per-module optimization levels; hopefully pretty soon we'll be able to apply -O0 to the whole package.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 17, 2020

Presumably some of that compile time is for the actual @avxed methods, and I wonder if those still benefit from aggressive compilation. Or if, in fact, LoopVectorization is the compiler here and anything else trying to get fancy is just getting in the way.

@JeffBezanson
Copy link
Sponsor Member

I think I figured out the unusually large codegen time. It's all in emit_llvmcall, presumably from SIMDPirates. It's not common for code to use a huge number of llvmcalls, so it hasn't been optimized in the compiler much. A lot of time is just in generating unique names and parsing the llvm.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 17, 2020

In my local tests (just running the profiler) I'm seeing a bit larger fraction for inference; a bit over 7/25ths of the time is in typeinf and an approximately equivalent amount in emit_call. I don't doubt you have better ways of timing this, but I thought I should mention it.

julia> versioninfo()
Julia Version 1.5.0-DEV.458
Commit fa5e92445c* (2020-03-14 19:10 UTC)
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
Environment:
  JULIA_CPU_THREADS = 4

and master on LoopVectorization and VectorizationBase.

@timholy
Copy link
Sponsor Member Author

timholy commented Mar 18, 2020

Also worth noting that LoopVectorization has a large precompile file to try to reduce the amount of time spent on inference. Since that precompile file was generated by running the tests, it's essentially hand-crafted to minimize the amount of inference time while running the tests. This will not, however, be applicable for real-world uses of LoopVectorization since consumers can't require precompilation of methods of LoopVectorization itself.

Master is especially relevant because of JuliaSIMD/LoopVectorization.jl#75, that added some type parameters to a type that doesn't have them in released versions of the package.

@chriselrod
Copy link
Contributor

chriselrod commented Mar 18, 2020

LoopVectorization does a lot of work to generate the functions, including iterating over a lot of loop orders and unrolling combinations, and running the cost model on each of them.
I had thought at least a little optimization may be important, but if only up to 5% of time is being spent their, evidently they're fast enough and (see below) I'm empirically seeing the best total runtime with -O0.

If per module compilation were implemented, and someone called a generated function from the module, how would the generated code be optimized? While O0 or maybe O1 would be best for code generation, I still need the actual generated code to be compiled with O2 or O3.
(Just tried a simple example and saw a 20x runtime performance degradation between O1 and O3.)

While I'm not using LLVM for autovectorization, I'm still relying on it for things like dead code elimination and inst combine, and not worrying about making indexing efficient.
As a recent example, to improve some image filtering benchmarks, I updated the cost modeling to say that loads are cheaper when LLVM can eliminate redundant redundant loads, but I'm still generating all the redundant loads and counting on LLVM to eliminate them.

But I guess this would be as simple to solve as having multiple modules within LoopVectorization, so that _avx_! could be by itself in one with -O3, and call the code sitting in an O0 module to generate the body.

Quickly testing after deleting the body of the precompile function,
julia -O0 -E 'include("/home/chriselrod/.julia/dev/LoopVectorization/test/runtests.jl")'
Yields:

  • -O0: 209.726632 seconds (754.28 M allocations: 32.826 GiB, 3.57% gc time)
  • -O1: 214.267720 seconds (754.28 M allocations: 32.826 GiB, 3.37% gc time)
  • -O2: 263.422277 seconds (754.23 M allocations: 32.824 GiB, 2.70% gc time)
  • -O3: 262.814417 seconds (754.23 M allocations: 32.824 GiB, 2.70% gc time)

Out of curiosity, any idea how LLVM.jl's performance compares with llvmcall?
It may be worth considering a transition, because I could also get invariant intrinsics to work with the former but not the latter, despite having much more experience with llvmcall.

@timholy
Copy link
Sponsor Member Author

timholy commented Apr 14, 2020

It seems time for an update here. Using a recent Julia master with ENABLE_TIMINGS defined, and LoopVectorization 0.7.0 (which has now essentially ditched its precompile file):

Test Summary:        | Pass  Total
LoopVectorization.jl | 1091   1091
237.992516 seconds (751.83 M allocations: 33.212 GiB, 2.99% gc time)
ROOT                      :  0.30 %   1889389130
GC                        :  3.02 %   18939286574
LOWERING                  :  5.50 %   34442303902
PARSING                   :  0.04 %   280687890
INFERENCE                 : 19.09 %   119621651748
CODEGEN                   : 10.44 %   65429822168
METHOD_LOOKUP_SLOW        :  0.05 %   305654374
METHOD_LOOKUP_FAST        :  3.53 %   22123410322
LLVM_OPT                  : 51.12 %   320337754472
LLVM_MODULE_FINISH        :  0.43 %   2712265790
LLVM_EMIT                 :  0.01 %   39088412
METHOD_LOOKUP_COMPILE     :  0.00 %   26002236
METHOD_MATCH              :  2.14 %   13440216708
TYPE_CACHE_LOOKUP         :  1.07 %   6735874132
TYPE_CACHE_INSERT         :  0.00 %   12747630
STAGED_FUNCTION           :  0.20 %   1283430378
MACRO_INVOCATION          :  0.02 %   105796960
AST_COMPRESS              :  1.41 %   8848165808
AST_UNCOMPRESS            :  1.45 %   9074990684
SYSIMG_LOAD               :  0.03 %   182878746
ADD_METHOD                :  0.11 %   671121294
LOAD_MODULE               :  0.03 %   162538790
INIT_MODULE               :  0.00 %   6181898

So about 30% on lowering+methodlookup+inference and 50% LLVM.

Here's a summary of what SnoopCompile tells me of where it's spending its inference time:

  • many of the biggest offenders are the "real functions" in the tests (e.g., avx2d!(::OffsetArray{Float32,2,Array{Float32,2}}, ::Array{Float32,2}, ::OffsetArray{Float32,2,Array{Float32,2}})) takes ~3s, but no other case is longer than 0.7s). This seems expected.
  • for methods that are defined in LoopVectorization itself,
    • two calls to _avx_! (a @generated function) each weigh in at 0.7s and 0.8s. But no more _avx_! calls show up among those taking more than 0.01s, so it's not the case that an insane number of _avx_! methods are being inferred.
    • variants of vmaterialize and vmaterialize!, @generated vectorized broadcasting methods, account for 30 of the next ~35 most expensive inference cases, each requiring 0.17-0.37s. This seems inevitable given that these methods affect runtime performance.
    • there were many variants of avx_loopset and _avx_loopset being inferred, all corresponding to different vargs types. That's fixable with @nospecialize (see Add nospecialize and precompiles to reduce latency JuliaSIMD/LoopVectorization.jl#97)

At this point I'm reasonably satisfied that there is no obvious low-hanging fruit. I'm fine with seeing this issue closed.

@timholy
Copy link
Sponsor Member Author

timholy commented May 8, 2020

Can anyone else confirm this?

tim@diva:~/src/julia-master$ time julia --startup-file=no -e 'println(VERSION); using LoopVectorization'
1.4.2-pre.0

real	0m6.005s
user	0m6.042s
sys	0m0.252s
tim@diva:~/src/julia-master$ time julia-master --startup-file=no -e 'println(VERSION); using LoopVectorization'
1.5.0-DEV.875

real	0m0.666s
user	0m0.739s
sys	0m0.221s

I first thought I'd slipped a decimal point.

If that's true, it's absolutely incredible!

@chriselrod
Copy link
Contributor

On 1.1:

> time bin/julia --startup-file=no -e 'println(VERSION); using LoopVectorization'
1.1.0

________________________________________________________
Executed in  858.02 millis    fish           external
   usr time  1046.53 millis    0.00 micros  1046.53 millis
   sys time  364.15 millis  1454.00 micros  362.70 millis

Master:

> time julia --startup-file=no -e 'println(VERSION); using LoopVectorization'
1.5.0-DEV.865

________________________________________________________
Executed in  616.09 millis    fish           external
   usr time  808.87 millis  203.00 micros  808.67 millis
   sys time  361.74 millis  1274.00 micros  360.46 millis

This is around a 20% improvement, but much less than 10x.
Could it have been precompiling on 1.4? Did you try several times?

How reliable is timing Travis runs between versions? Julia 1.4's tests complete at least 10% faster than 1.1.
But I've noticed weird things on Travis, like compiled code seemingly running 2.5 times faster on 1.4 than 1.5, making it look like it's either just very noisy, or maybe some settings are different.

@timholy
Copy link
Sponsor Member Author

timholy commented May 8, 2020

Definitely already precompiled on 1.4. I also just ran a fresh compilation on 1.4 and got the same result. I did not try Julia 1.1.

EDIT: actually, a recompile fixed it. When I first tested the recompile I inadvertently ran the benchlv.jl script on 1.4. Here are the real results:

tim@diva:~/src/julia-1$ time julia --startup-file=no -e 'println(VERSION); using LoopVectorization'
1.4.2-pre.0

real	0m0.990s
user	0m1.070s
sys	0m0.212s

which is much more in line with my general experience.

Earlier today I was messing around with PackageCompiler. Nominally I had restored the original system image but maybe it changed something.

@Sacha0
Copy link
Member

Sacha0 commented Sep 3, 2020

The specializations in that table have been inferred, but not compiled. We could add some kind of @noinfer maybe, which would be much more drastic.

Hm, perhaps this snippet from the manual section Be aware of when Julia avoids specializing is slightly confusing then?

"Note that @code_typed and friends will always show you specialized code, even if Julia would not normally specialize that method call. You need to check the [method internals](@ref ast-lowered-method) if you want to see whether specializations are generated when argument types are changed, i.e., if (@which f(...)).specializations contains specializations for the argument in question."

In that the distinction between inferred and compiled isn't explicit? Or am I not following? :)

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 a pull request may close this issue.

6 participants