在Julia中将函数参数更改为关键字似乎会引入类型不稳定性

在Julia中将函数参数更改为关键字似乎会引入类型不稳定性,julia,keyword-argument,type-stability,Julia,Keyword Argument,Type Stability,我有一个程序,其中main()函数有四个参数。当我在函数上运行@code\u warntype时,似乎没有什么问题。所有变量都有指定的类型,并且没有UNION实例或其他明显的警告标志 抱歉,该程序相当长,但我不确定如何在保留问题的同时缩短它: function main(n::Int, dice::Int=6, start::Int=1, modal::Int=3) ::Tuple{String, Vector{String}, Vector{Float64}} board = Stri

我有一个程序,其中
main()
函数有四个参数。当我在函数上运行
@code\u warntype
时,似乎没有什么问题。所有变量都有指定的类型,并且没有
UNION
实例或其他明显的警告标志

抱歉,该程序相当长,但我不确定如何在保留问题的同时缩短它:

function main(n::Int, dice::Int=6, start::Int=1, modal::Int=3) ::Tuple{String, Vector{String}, Vector{Float64}}
    board = String["GO", "A1", "CC1", "A2", "T1", "R1", "B1", "CH1", "B2", "B3",
        "JAIL", "C1", "U1", "C2", "C3", "R2", "D1", "CC2", "D2", "D3",
        "FP", "E1", "CH2", "E2", "E3", "R3", "F1", "F2", "U2", "F3",
        "G2J", "G1", "G2", "CC3", "G3", "R4", "CH3", "H1", "T2", "H2"]
    cc_cards = shuffle(collect(1:16))
    ch_cards = shuffle(collect(1:16))
    function take_cc_card(square::Int, cards::Vector{Int})::Tuple{Int, Vector{Int}}
        if cards[1] == 1
            square = findfirst(board, "GO")
        elseif cards[1] == 2
            square = findfirst(board, "JAIL")
        end
        p = pop!(cards)
        unshift!(cards, p)
        return square, cards
    end
    function take_ch_card(square::Int, cards::Vector{Int})::Tuple{Int, Vector{Int}}
        if cards[1] == 1
            square = findfirst(board, "GO")
        elseif cards[1] == 2
            square = findfirst(board, "JAIL")
        elseif cards[1] == 3
            square = findfirst(board, "C1")
        elseif cards[1] == 4
            square = findfirst(board, "E3")
        elseif cards[1] == 5
            square = findfirst(board, "H2")
        elseif cards[1] == 6
            square = findfirst(board, "R1")
        elseif cards[1] == 7 || cards[1] == 8
            if board[square] == "CH1"
                square = findfirst(board, "R2")
            elseif board[square] == "CH2"
                square = findfirst(board, "R3")
            elseif board[square] == "CH3"
                square = findfirst(board, "R1")
            end
        elseif cards[1] == 9
            if board[square] == "CH1"
                square = findfirst(board, "U1")
            elseif board[square] == "CH2"
                square = findfirst(board, "U2")
            elseif board[square] == "CH3"
                square = findfirst(board, "U1")
            end
        elseif cards[1] == 10
            square = (square - 3) % 40 + ((square - 3 % 40 == 0 ? 40 : 0))
        end
        p = pop!(cards)
        unshift!(cards, p)
        return square, cards
    end
    result = zeros(Int, 40)
    consec_doubles = 0
    square = 1
    for i = 1:n
        throw_1 = rand(collect(1:dice))
        throw_2 = rand(collect(1:dice))
        if throw_1 == throw_2
            consec_doubles += 1
        else
            consec_doubles = 0
        end
        if consec_doubles != 3
            move = throw_1 + throw_2
            square = (square + move) % 40 +((square + move) % 40 == 0 ? 40 : 0)
            if board[square] == "G2J"
                square = findfirst(board, "JAIL")
            elseif board[square][1:2] == "CC"
                square, cc_cards = take_cc_card(square, cc_cards)
            elseif board[square][1:2] == "CH"
                square, ch_cards = take_ch_card(square, ch_cards)
                if board[square][1:2] == "CC"
                    square, cc_cards = take_cc_card(square, cc_cards)
                end
            end
        else
            square = findfirst(board, "JAIL")
            consec_doubles = 0
        end
        if i >= start
            result[square] += 1
        end
    end
    result_tuple = Vector{Tuple{Float64, Int}}()
    for i = 1:40
        percent = result[i] * 100 / sum(result)
        push!(result_tuple, (percent, i))
    end
    sort!(result_tuple, lt = (x, y) -> isless(x[1], y[1]), rev=true)
    modal_squares = Vector{String}()
    modal_string = ""
    modal_percents = Vector{Float64}()
    for i = 1:modal
        push!(modal_squares, board[result_tuple[i][2]])
        push!(modal_percents, result_tuple[i][1])
        k = result_tuple[i][2] - 1
        modal_string *= (k < 10 ? ("0" * string(k)) : string(k))
    end
    return modal_string, modal_squares, modal_percents
end

@code_warntype main(1_000_000, 4, 101, 5)
…我似乎遇到了类型稳定性问题

@code_warntype main(1_000_000, dice=4, start=101, modal=5)
现在,当我运行
@code\u warntype
时,我在主文本中得到一个临时变量,其类型为
ANY
UNION

奇怪的是,这似乎并没有带来性能上的影响,因为在平均三次基准测试中,“参数”版本的运行时间为431.594毫秒,“关键字”版本的运行时间为413.149毫秒。然而,我很想知道:

(a) 为什么会发生这种情况

(b) 作为一般规则,具有
ANY
类型的临时变量的出现是否值得关注;及


(c) 一般来说,从性能角度来看,使用关键字而不是普通函数参数是否有任何好处。

以下是我对这三个问题的看法。在回答中,我假设Julia为0.6.3,除非我在文章末尾明确指出我提到了Julia 0.7

(a) 带有
Any
变量的代码是负责处理关键字参数的代码的一部分(例如,确保函数签名允许传递关键字参数)。原因是关键字参数在函数中作为
Vector{Any}
接收。向量包含元组
([参数名称],[参数值])
。 函数的实际“功”发生在该部分之后,带有
任何
变量

您可以通过比较调用看到这一点:

@code_warntype main(1_000_000, dice=4, start=101, modal=5)

对于具有关键字参数的函数。第二个调用只有上面第一个调用生成的报告的最后一行,所有其他调用负责处理传递的关键字参数

(b) 作为一般规则,这当然是一个问题,但在这种情况下,这是无法帮助的。带有
Any
的变量保存有关关键字参数名称的信息

(c) 通常,您可以假设位置参数不比关键字参数慢,但可以更快。这是一个MWE(实际上,如果您运行
@code\u warntype f(a=10)
,您也会看到这个
任何
变量):

现在您可以看到,实际上关键字参数的惩罚是当它被传递时(这正是当您在
@code\u warntype
中有
任何
变量时的情况,因为Julia需要做更多的工作)。请注意,惩罚很小,在做很少工作的函数中可以看到。对于进行大量计算的函数,它在大多数情况下可以忽略

另外请注意,如果您不指定关键字参数的类型,那么当显式传递关键字参数值时,惩罚会大得多,因为Julia不会对关键字参数类型进行分派(您也可以运行
@code\u warntype
来见证这一点):

在Julia 0.7中,关键字参数作为
Base.Iterator.Pairs
接收,其中包含
NamedTuple
,因此Julia知道编译时传递的参数类型。这意味着使用关键字参数比使用Julia 0.6.3更快(但同样,您不应该期望它们比位置参数更快)。你可以看到这个buy运行类似的基准测试(我只是稍微改变了函数的功能,为Julia编译器提供了更多的工作),但是在Julia 0.7下(你也可以看看这些函数的
@code\u warntype
,看看类型推断在Julia 0.7中工作得更好):


非常感谢,我非常感谢您花时间回答一个小的技术问题,这个问题可能不是全世界都感兴趣的(我想知道“引擎盖下”发生了什么)。我特别感谢MWE,我可以在自己的系统上轻松复制。我目前没有运行0.7(我使用的JuliaPro仍然是0.6.3),但我期待着改进。
@code_warntype main(1_000_000, dice=4, start=101, modal=5)
@code_warntype main(1_000_000)
julia> using BenchmarkTools

julia> f(;a::Int=1) = a+1
f (generic function with 1 method)

julia> g(a::Int=1) = a+1
g (generic function with 2 methods)

julia> @benchmark f()
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.865 ns (0.00% GC)
  median time:      1.866 ns (0.00% GC)
  mean time:        1.974 ns (0.00% GC)
  maximum time:     14.463 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark f(a=10)
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     52.994 ns (0.00% GC)
  median time:      54.413 ns (0.00% GC)
  mean time:        65.207 ns (10.65% GC)
  maximum time:     3.466 μs (94.78% GC)
  --------------
  samples:          10000
  evals/sample:     986

julia> @benchmark g()
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.865 ns (0.00% GC)
  median time:      1.866 ns (0.00% GC)
  mean time:        1.954 ns (0.00% GC)
  maximum time:     13.062 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark g(10)
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.865 ns (0.00% GC)
  median time:      1.866 ns (0.00% GC)
  mean time:        1.949 ns (0.00% GC)
  maximum time:     13.063 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000
julia> h(;a=1) = a+1
h (generic function with 1 method)

julia> @benchmark h()
BenchmarkTools.Trial:
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.865 ns (0.00% GC)
  median time:      1.866 ns (0.00% GC)
  mean time:        1.960 ns (0.00% GC)
  maximum time:     13.996 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark h(a=10)
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     75.433 ns (0.00% GC)
  median time:      77.355 ns (0.00% GC)
  mean time:        89.037 ns (7.87% GC)
  maximum time:     2.128 μs (89.73% GC)
  --------------
  samples:          10000
  evals/sample:     971
julia> using BenchmarkTools

julia> f(;a::Int=1) = [a]
f (generic function with 1 method)

julia> g(a::Int=1) = [a]
g (generic function with 2 methods)

julia> h(;a=1) = [a]
h (generic function with 1 method)

julia> @benchmark f()
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     31.724 ns (0.00% GC)
  median time:      34.523 ns (0.00% GC)
  mean time:        50.576 ns (22.80% GC)
  maximum time:     53.465 μs (99.89% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark f(a=10)
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     31.724 ns (0.00% GC)
  median time:      34.057 ns (0.00% GC)
  mean time:        50.739 ns (22.83% GC)
  maximum time:     55.303 μs (99.89% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark g()
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     31.724 ns (0.00% GC)
  median time:      34.523 ns (0.00% GC)
  mean time:        50.529 ns (22.77% GC)
  maximum time:     54.501 μs (99.89% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark g(10)
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     31.724 ns (0.00% GC)
  median time:      34.523 ns (0.00% GC)
  mean time:        50.899 ns (23.27% GC)
  maximum time:     56.246 μs (99.90% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark h()
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     31.257 ns (0.00% GC)
  median time:      34.057 ns (0.00% GC)
  mean time:        50.924 ns (22.87% GC)
  maximum time:     55.724 μs (99.88% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark h(a=10)
BenchmarkTools.Trial:
  memory estimate:  96 bytes
  allocs estimate:  1
  --------------
  minimum time:     31.724 ns (0.00% GC)
  median time:      34.057 ns (0.00% GC)
  mean time:        50.864 ns (22.60% GC)
  maximum time:     53.389 μs (99.83% GC)
  --------------
  samples:          10000
  evals/sample:     1000