我有两个似乎执行相同操作的代码版本:
sum = 0
for x in 1:100
sum += x
end
sum = 0
for x in collect(1:100)
sum += x
end
两种方法之间是否有实际区别?
答案 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和任意(有限)大小的范围。 UnitRange
是AbstractRange
的子类型,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