UnitRange和Array有什么区别?

时间:2019-09-18 21:38:07

标签: julia

我有两个似乎执行相同操作的代码版本:

sum = 0
for x in 1:100
    sum += x
end
sum = 0
for x in collect(1:100)
    sum += x
end

两种方法之间是否有实际区别?

2 个答案:

答案 0 :(得分:4)

UnitRange将使用恒定的空间,而Array将与您要遍历的元素数量成比例增长。

您可以使用BenchmarkTools软件包查看区别。

julia> @btime sum(1:1000000)
  0.044 ns (0 allocations: 0 bytes)
500000500000

julia> @btime sum(collect(1:1000000))
  1.123 ms (2 allocations: 7.63 MiB)
500000500000

这两种方法在时空性能上都存在显着差异。 UnitRange避免了在一次不需要全部内存时分配大量内存的工作。尽可能在UnitRange上使用Array

答案 1 :(得分:3)

在Julia中,1:100返回一个称为UnitRange的特定结构,如下所示:

julia> dump(1:100)
UnitRange{Int64}
  start: Int64 1
  stop: Int64 100

这是一个非常紧凑的结构,用于表示具有步骤1和任意(有限)大小的范围。 UnitRangeAbstractRange的子类型,AbstractVector的子类型是表示具有任意步长的范围的类型。

只要您使用UnitRange(或语法糖getindex),vector[index]的实例就会动态计算其元素。例如,使用@less (1:100)[3],您可以看到此方法:

function getindex(v::UnitRange{T}, i::Integer) where {T<:OverflowSafe}
    @_inline_meta
    val = v.start + (i - 1)
    @boundscheck _in_unit_range(v, val, i) || throw_boundserror(v, i)
    val % T
end

这是通过将i添加到范围的第一个元素(i - 1)来返回向量的第start个元素。某些功能使用UnitRange,或更普遍地使用AbstractRange优化了方法。例如,使用@less sum(1:100),您可以看到以下内容

function sum(r::AbstractRange{<:Real})
    l = length(r)
    # note that a little care is required to avoid overflow in l*(l-1)/2
    return l * first(r) + (iseven(l) ? (step(r) * (l-1)) * (l>>1)
                                     : (step(r) * l) * ((l-1)>>1))
end

此方法使用formula for the sum of an arithmetic progression,这是非常有效的,因为它在与向量大小无关的时间内进行评估。

另一方面,collect(1:100)返回具有{100、1、2、3,...,100个元素的普通Vector。与UnitRange(或其他类型)的主要区别AbstractRange的值是getindex(vector::Vector, i)(或vector[i],带有vector::Vector)不进行任何计算,而只是访问向量的第i个元素。 Vector的缺点是UnitRange的缺点是,通常来说,使用它们时没有有效的方法,因为此容器的元素是完全任意的,而UnitRange代表一组具有特殊属性(排序,恒定步长等)的数字。

如果您比较UnitRange具有超高效实现的方法的性能,则这种类型将毫无用处(请注意,在使用{{1}中的宏时,使用$(...)进行变量插值}}):

BenchmarkTools

请记住,每次使用julia> using BenchmarkTools julia> @btime sum($(1:1000_000)) 0.012 ns (0 allocations: 0 bytes) 500000500000 julia> @btime sum($(collect(1:1000_000))) 229.979 μs (0 allocations: 0 bytes) 500000500000 访问元素时,UnitRange都会带来动态计算元素的成本。例如考虑以下功能:

getindex

让我们用function test(vec) sum = zero(eltype(vec)) for idx in eachindex(vec) sum += vec[idx] end return sum end 和普通的UnitRange对其进行基准测试:

Vector

在这种情况下,调用普通数组的函数比使用julia> @btime test($(1:1000_000)) 812.673 μs (0 allocations: 0 bytes) 500000500000 julia> @btime test($(collect(1:1000_000))) 522.828 μs (0 allocations: 0 bytes) 500000500000 的函数要快,因为它不必动态计算100万个元素。

当然,在这些玩具示例中,对UnitRange的所有元素进行迭代而不是对其索引进行索引更为明智,但在现实世界中,这种情况可能更明智。但是,最后一个示例显示vec不一定比普通数组更有效,尤其是在需要动态计算其所有元素的情况下。如果您可以利用可以在恒定时间内执行操作的专用方法(例如UnitRange),则UnitRange效率更高。

作为文件备注,请注意,如果您最初有一个sum,将其转换为普通的UnitRange以获得良好的性能并不一定是一个好主意,特别是如果您要使用只需一次或很少几次,因为到Vector的转换本身就涉及到范围内所有元素的动态计算以及必要内存的分配:

Vector