5

当类型推断失败时(::Any@code_warntype打印输出中),我的理解是函数调用是动态调度的。换句话说,在运行时,会检查参数的类型以找到MethodInstance具体参数类型的特化 ( )。需要在运行时而不是编译时执行此操作会导致性能成本。

(编辑:最初,我在类型检查和专业化查找之间说“多次调度找到合适的方法”,但我实际上不知道这部分是否在运行时发生。似乎只有在没有有效的情况下才需要发生存在专业化,需要编译。)

在只需要检查一个参数的具体类型的情况下,是否可以进行更快的动态单次调度,例如在某种专业化查找表中?我只是找不到MethodInstance像函数一样访问和调用 s 的方法。

当谈到改变调度或专业化时,我想到了invokeand @nospecializeinvoke看起来它可能会直接跳到指定的方法,但仍然必须检查多个参数类型和专业化。@nospecialize不会跳过调度过程的任何部分,只会导致不同的专业化。

编辑:一个带有评论的最小示例,希望能描述我在说什么。

struct Foo end
struct Bar end

#   want to dispatch only on 1st argument
#          still want to specialize on 2nd argument
baz(::Foo, ::Integer) = 1
baz(::Foo, ::AbstractFloat) = 1.0
baz(::Bar, ::Integer) = 1im
baz(::Bar, ::AbstractFloat) = 1.0im

x = Any[Foo(), Bar(), Foo()]

# run test1(x, 1) or test1(x, 1.0)
function test1(x, second)
  #   first::Any in @code_warntype printout
  for first in x
    # first::Any requires dynamic dispatch of baz
    println(baz(first, second))
    # Is it possible to only dispatch -baz- on -first- given
    # the concrete types of the other arguments -second-?
  end
end
4

1 回答 1

0

执行您所要求的最简单的方法是简单地不对第二个参数进行分派(通过未在第二个变量上指定足以触发分派的类型断言),而是专门使用if函数中的语句。例如:

struct Foo end
struct Bar end

# Note lack of type assertion on second variable. 
# We could also write `baz(::Foo, n::Number)` for same effect in this case, 
# but type annotations have no performance benefit in Julia if you're not 
# dispatching on them anyways.
function baz(::Foo, n) 
    if isa(n, Integer)
        1
    elseif isa(n, AbstractFloat)
        1.0
    else
        error("unsupported type")
    end
end

function baz(::Bar, n)
    if isa(n, Integer)
        1im
    elseif isa(n, AbstractFloat)
        1.0im
    else
        error("unsupported type")
    end
end

现在,这将做你想要的

julia> x = Any[Foo(), Bar(), Foo()]
3-element Vector{Any}:
 Foo()
 Bar()
 Foo()

julia> test1(x, 1)
1
0 + 1im
1

julia> test1(x, 1.0)
1.0
0.0 + 1.0im
1.0

并且由于这有效地从所有可能的类型中手动选择了两个案例进行专门化,我可以想象这种技术具有性能优势的场景(当然,在 Julia 中不用说通常会更好如果可能的话,首先要找到并消除类型不稳定性的根源)。

然而,在这个问题的上下文中,重要的是要指出即使我们已经消除了函数的第二个参数的调度,baz如果第一个参数(即你are dispatching on) 是类型不稳定的——就像问题中的情况一样,因为使用了Array{Any}.

相反,请尝试使用至少具有某种类型约束的数组。前任:

julia> function test2(x, second)
           s = 1+1im
           for first in x
               s += baz(first, second)
           end
           s
       end
test2 (generic function with 1 method)

julia> using BenchmarkTools

julia> x = Any[Foo(), Bar(), Foo()];

julia> @benchmark test2($x, 1)
BenchmarkTools.Trial: 10000 samples with 998 evaluations.
 Range (min … max):  13.845 ns … 71.554 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     13.869 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   15.397 ns ±  3.821 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▅  ▃ ▄  ▄      ▄       ▄                                 ▃ ▁
  ██▇▆█▇██▄█▇▇▄▃▁▁██▁▃▃▁▁▃██▃▁▃▁▁▄▃▃▃▆▆▅▆▆▅▅▄▁▁▄▃▃▃▁▃▁▄▁▁▃▄▄█ █
  13.8 ns      Histogram: log(frequency) by time      30.2 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> x = Union{Foo,Bar}[Foo(), Bar(), Foo()];

julia> @benchmark test2($x, 1)
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max):  4.654 ns … 62.311 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     4.707 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   5.471 ns ±  1.714 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▂▂▃▄ ▃  ▄▁    ▄▂      ▅▁                               ▁▄ ▁
  ███████▁▁██▁▁▁▁██▁▁▁▁▁▁██▁▁▁▄▁▃▁▃▁▁▁▁▃▁▁▁▁▃▁▃▃▁▁▁▁▃▁▁▁▁▁██ █
  4.65 ns      Histogram: log(frequency) by time     10.2 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.
于 2022-01-06T21:17:55.840 回答