Julia 函数参数的就地更新

Julia 函数参数的就地更新,julia,Julia,我需要一个有效的实现笛卡尔积为可变数量的数组 我尝试了Iterators.jl中的product函数,但性能不高 我是一名python黑客,使用过sklearn,并获得了良好的性能结果 我曾尝试编写此函数的Julia版本,但无法产生与python函数相同的结果 我的代码是: function my_repeat(a, n) # mimics numpy.repeat m = size(a, 1) out = Array(eltype(a), n * m) out[

我需要一个有效的实现笛卡尔积为可变数量的数组

我尝试了
Iterators.jl
中的
product
函数,但性能不高

我是一名python黑客,使用过sklearn,并获得了良好的性能结果

我曾尝试编写此函数的Julia版本,但无法产生与python函数相同的结果

我的代码是:

function my_repeat(a, n)
    # mimics numpy.repeat
    m = size(a, 1)
    out = Array(eltype(a), n * m)
    out[1:n] = a[1]
    for i=2:m
        out[(i-1)*n+1:i*n] = a[i]
    end
    return out
end

function cartesian(arrs; out=None)
    dtype = eltype(arrs[1])

    n = prod([size(i, 1) for i in arrs])

    if is(out, None)
        out = Array(dtype, n, length(arrs))
    end

    m = int(n / size(arrs[1], 1))
    out[:, 1] = my_repeat(arrs[1], m)

    if length(arrs[2:]) > 0
        cartesian(arrs[2:], out=out[1:m, 2:])
        for j = 1:size(arrs[1], 1)-1
            out[(j*m + 1):(j+1)*m, 2:] = out[1:m, 2:]
        end
    end

    return out
end
我用以下方法测试它:

aa = ([1, 2, 3], [4, 5], [6, 7])
cartesian(aa)
out_end = size(out, 2)
cartesian(arrs[2:], out=sub(out, 1:m, 2:out_end))
返回值为:

12x3 Array{Float64,2}:
1.0  9.88131e-324  2.13149e-314
1.0  2.76235e-318  2.13149e-314
1.0  9.88131e-324  2.13676e-314
1.0  9.88131e-324  2.13676e-314
2.0  9.88131e-324  2.13149e-314
2.0  2.76235e-318  2.13149e-314
2.0  9.88131e-324  2.13676e-314
2.0  9.88131e-324  2.13676e-314
3.0  9.88131e-324  2.13149e-314
3.0  2.76235e-318  2.13149e-314
3.0  9.88131e-324  2.13676e-314
3.0  9.88131e-324  2.13676e-314
我相信这里的问题是,当我使用这一行时:
cartesian(arrs[2:],out=out[1:m,2:])
,关键字参数
out
在递归调用中没有就地更新

可以看出,我对这个函数的Python版本做了一个非常简单的翻译(参见上面的链接)。很可能是由于内部语言的差异,无法进行简单的翻译。我不认为这是真的,因为julia文档的这一部分引用了这句话:

Julia函数参数遵循一种有时称为“通过共享传递”的约定,这意味着值在传递给函数时不会被复制。函数参数本身充当新的变量绑定(可以引用值的新位置),但它们引用的值与传递的值相同。调用方可以看到在函数中对可变值(如数组)所做的修改。这与Scheme、大多数Lisp、Python、Ruby和Perl以及其他动态语言中的行为相同

如何使这个(或等效的)函数在Julia中工作?

我找到了答案

这并不是Julia不更新函数参数的问题,而是使用slice操作符
a[ind]
的问题,它生成数据的副本,而不是通过引用索引我的数组。文件的这一部分给出了答案:

子数组是AbstractArray的一种特殊化,它通过引用而不是复制来执行索引。子数组是使用子函数创建的,其调用方式与getindex相同(使用数组和一系列索引参数)。sub的结果看起来与getindex的结果相同,只是数据保留在原位。sub将输入索引向量存储在子数组对象中,该子数组对象稍后可用于间接索引原始数组

通过将此行从以下位置更改,问题已得到解决:

cartesian(arrs[2:], out=out[1:m, 2:])
对下列事项:

aa = ([1, 2, 3], [4, 5], [6, 7])
cartesian(aa)
out_end = size(out, 2)
cartesian(arrs[2:], out=sub(out, 1:m, 2:out_end))

在Base中有一个
repeat
函数

更短更快的变体可能使用笛卡尔软件包中的
@forcartesian
宏:

using Cartesian

function cartprod(arrs, out=Array(eltype(arrs[1]), prod([length(a) for a in arrs]), length(arrs)))
    sz = Int[length(a) for a in arrs]
    narrs = length(arrs)
    @forcartesian I sz begin
        k = sub2ind(sz, I)
        for i = 1:narrs
            out[k,i] = arrs[i][I[i]]
        end
    end
    out
end

行的顺序与您的解决方案不同,但也许这并不重要?

这是一个老问题,但随着Julia的进步,答案也发生了变化

基本问题是像
a[1:3,:]
这样的切片需要复制。如果在函数中更新该副本,它对
a
本身没有影响

现代的答案是使用
@view a[1:3,:]
获取对底层数组部分的引用。此视图的更新将反映在基础阵列中

您可以强制整个代码块在
@views
宏中使用视图语义


有关更多讨论,请参阅。

我认为slice操作符将在Julia的未来版本中返回子数组,但这可能不会破坏此代码。很高兴知道。感谢您的提醒,因为编译器无法推断出
out
的类型(它可能是
None
,也可能是
Array
),所以无论您如何处理数组切片,这里的性能都会有问题。更好的方法可能是避免第二个参数的关键字(只需将分号改为逗号),并将其初始化为
Array(eltype(arrs[1])、prod([size(i,1)表示arrs中的i])、length(arrs))
。这非常有效。谢谢你的回答。我刚刚查看了它,笛卡尔软件包非常棒(尽管我需要花一些时间来思考如何使用这些宏)!是否有可能进一步优化
out[k,i]=arrs[i][i[i]]
以避免在这个最内部的循环中执行4个切片操作?(这个想法来自于我的Python背景,在紧密的内部循环中切片成本相当高)FYI,对于一些非常简单的配置文件,如
cartesian([[1:j]对于i=1:k])
,我的函数的性能比这个函数的性能差1.1-1.5倍,这取决于
i
j
的值。所以这是一个进步!然而,这并没有我想象的那么大的改善。如果您使用的是小型阵列,那么如何解决这个问题可能无关紧要。如果您对性能非常敏感,那么可以对其进行安排,以便对每个项目执行一次切片。查看
@nloops
宏,特别是它的
preexpr
形式。