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
形式。