如何写"好"处理多种类型和数组时的Julia代码(多次调度)

时间:2014-07-29 06:02:15

标签: arrays types julia multiple-dispatch

我是朱莉娅的新手,鉴于我的Matlab起源,我在确定如何编写"好" Julia代码利用了多个调度和Julia的类型系统。

考虑我有一个提供Float64的平方的函数的情况。我可以写成:

function mysquare(x::Float64)
    return(x^2);
end

有时,我希望将所有Float64放在一维数组中,但不想每次都在mysquare上写出一个循环,所以我使用多个调度和添加以下内容:

function mysquare(x::Array{Float64, 1})
    y = Array(Float64, length(x));
    for k = 1:length(x)
        y[k] = x[k]^2;
    end
    return(y);
end

但是现在我有时和Int64一起工作,所以我写了两个利用多个调度的函数:

function mysquare(x::Int64)
    return(x^2);
end
function mysquare(x::Array{Int64, 1})
    y = Array(Float64, length(x));
    for k = 1:length(x)
        y[k] = x[k]^2;
    end
    return(y);
end

这是对的吗?或者是否有更具思想性的方法来应对这种情况?我应该使用像这样的类型参数吗?

function mysquare{T<:Number}(x::T)
    return(x^2);
end
function mysquare{T<:Number}(x::Array{T, 1})
    y = Array(Float64, length(x));
    for k = 1:length(x)
        y[k] = x[k]^2;
    end
    return(y);
end

这感觉很明智,但是我的代码会像避免参数类型那样快速运行吗?

总之,我的问题分为两部分:

  1. 如果快速代码对我很重要,我应该如上所述使用参数类型,还是应该为不同的具体类型写出多个版本?或者我应该完全做其他事情吗?

  2. 当我想要一个对数组和标量进行操作的函数时,最好是编写两个版本的函数,一个用于标量,一个用于数组?或者我应该完全做其他事情吗?

  3. 最后,请指出您在上面的代码中可以想到的任何其他问题,因为我的最终目标是编写好的Julia代码。

    OP UPDATE:请注意,在最新版本的Julia(v0.5)中,回答此问题的惯用方法是定义mysquare(x::Number) = x^2。使用自动广播覆盖矢量化案例,即x = randn(5) ; mysquare.(x)

2 个答案:

答案 0 :(得分:42)

Julia根据需要为每组输入编译函数的特定版本。因此,回答第1部分,没有性能差异。参数方式是要走的路。

对于第2部分,在某些情况下编写单独的版本可能是个好主意(有时出于性能原因,例如,为了避免复制)。但是,在您的情况下,您可以使用内置宏@vectorize_1arg自动生成阵列版本,例如:

function mysquare{T<:Number}(x::T)
    return(x^2)
end
@vectorize_1arg Number mysquare
println(mysquare([1,2,3]))

至于一般风格,请勿使用分号,mysquare(x::Number) = x^2要短得多。

对于矢量化mysquare,请考虑TBigFloat的情况。但是,您的输出数组是Float64。处理此问题的一种方法是将其更改为

function mysquare{T<:Number}(x::Array{T,1})
    n = length(x)
    y = Array(T, n)
    for k = 1:n
        @inbounds y[k] = x[k]^2
    end
    return y
 end

我添加了@inbounds宏以提高速度,因为我们不需要每次都检查绑定的违规 - 我们知道长度。如果x[k]^2的类型不是T,则此功能仍可能存在问题。或许更具防御性的版本

function mysquare{T<:Number}(x::Array{T,1})
    n = length(x)
    y = Array(typeof(one(T)^2), n)
    for k = 1:n
        @inbounds y[k] = x[k]^2
    end
    return y
 end

one(T)如果1TInt1.0如果TFloat64,等等。这些注意事项只有在您想要创建超级健壮的库代码时才有意义。如果您真的只处理Float64或可以提升为Float64的事情,那么这不是问题。这似乎很辛苦,但力量是惊人的。您总是可以满足于类似Python的性能并忽略所有类型信息。

答案 1 :(得分:4)

从Julia 0.6(约于2017年6月)开始,“ dot syntax”提供了一种将函数应用于标量或数组的简便且惯用的方法。

您只需要提供函数的标量版本,即可使用正常方式编写。

function mysquare{x::Number)
    return(x^2)
end

在函数名称后附加.(或在操作符前加上)以在数组的每个元素上调用它:

x = [1 2 3 4]
x2 = mysquare(2)     # 4 
xs = mysquare.(x)    # [1,4,9,16]
xs = mysquare.(x*x') # [1 4 9 16; 4 16 36 64; 9 36 81 144; 16 64 144 256]
y  = x .+ 1          # [2 3 4 5]

请注意,如上例所示,电话会议将处理广播。

如果同一表达式中有多个点调用,则将它们融合在一起,以便y = sqrt.(sin.(x))进行一次通过/分配,而不是创建包含sin(x)的临时表达式并将其转发到sqrt ()功能。 (这与Matlab / Numpy / Octave / R不同,后者不提供此类保证)。

@.向量化一行中的所有内容,因此@. y=sqrt(sin(x))y = sqrt.(sin.(x))相同。这对于多项式特别方便,因为重复的点可能会使人感到困惑...