39

OP 更新:请注意,在最新版本的 Julia (v0.5) 中,回答这个问题的惯用方法是定义mysquare(x::Number) = x^2. 使用自动广播覆盖向量化的情况,即x = randn(5) ; mysquare.(x)。另请参阅更详细地解释点语法的新答案。

我是 Julia 的新手,并且鉴于我的 Matlab 起源,我在确定如何编写利用多重调度和 Julia 类型系统的“好” Julia 代码时遇到了一些困难。

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

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

有时,我想Float64在一维数组中对所有 s 进行平方,但不想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. 当我想要一个对数组和标量进行操作的函数时,编写两个版本的函数是否是一种好习惯,一个用于标量,一个用于数组?还是我应该完全做其他事情?

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

4

2 回答 2

43

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要短很多。

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

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如果is not的类型,此函数仍可能存在问题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

whereone(T)会给出1if Tis an Int1.0if Tis a Float64,依此类推。仅当您想要制作超健壮的库代码时,这些注意事项才重要。如果你真的只会处理Float64s 或可以提升为Float64s 的东西,那么这不是问题。看似辛苦,但力量却是惊人的。你总是可以满足于类似于 Python 的性能而忽略所有类型信息。

于 2014-07-29T18:53:39.613 回答
6

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

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

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

将 a 附加.到函数名称(或将其添加到运算符)以在数组的每个元素上调用它:

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/Python/R 不同,它们不做这样的保证)。

@.矢量化一行上的所有内容,因此@. y=sqrt(sin(x))y = sqrt.(sin.(x)). 这对于多项式特别方便,其中重复的点可能会令人困惑......

于 2020-02-22T02:03:42.177 回答