视图是提高代码性能的一种方法¶
本脚本将讨论视图的使用,视图是一种无需创建副本即可访问数组元素的机制。 将涉及以下主题
- 片拷贝(slicing)与视图(view)的区别
- 宏
@view
和@views
的使用及其区别。
为了检查使用视图的效率--让我们连接 BenchmarkTools 库
import Pkg; Pkg.add("BenchmarkTools")
复制问题¶
当我们使用b = a[1:5]
语法时,b 变成了a
前五个元素的副本,而不是 "按地址链接 "到a
的元素。
a = collect(1:10)
b = a[1:5] # [1, 2, 3, 4, 5]
println(pointer(a))
println(pointer(b))
b .= 0 # [0, 0, 0, 0, 0]
a' # как видим, матрица не поменялась, что и логично
为了改变原始矢量,我们不得不进行额外的操作:
a[1:5] .= b[1:5]
a'
视图函数¶
视图允许我们使用熟悉的语法,但创建的不是副本,而是直接访问数组的 "内存位置"。
为此,您可以使用函数view
a = collect(1:10000)
# '÷' не то же, что и '/' (÷ = div())
view_of_a = view(a,1:length(a)÷2) # end здесь не сработает
view_of_a .= 0
a
pointer(view_of_a) == pointer(a)
@allocated
,它可以显示已分配的字节数,从而确保使用视图可以避免为副本分配额外内存。
println(@allocated (subarray_of_a = a[1:end÷2]))
println(@allocated (view_of_a = view(a,1:length(a)÷2)))
@view¶
但使用view
函数并不符合上述关于 "熟悉的界面 "的说法,因为我们无法使用关键字end
等。
为了解决这个问题,我们可以使用宏@view
:
a = repeat(1:10,inner=3)
b = @view a[end-3:end]
b .= 0
a'
但问题可能会出现:既然我们可以直接使用
a = repeat(1:10,inner=3)
a[end-3,3] .= 0
答案如下:
作为有效利用资源和保持代码可读性的结合体,我们需要表征。
假设有一项任务需要输出和计算三元组元素的总和。
``朱莉娅 for i in 0:(length(a)÷3-1) println("sum of$(a[3i+1:3i+3]) -> $(sum(a[3i+1:3i+3]))")") 结束
可以看出,这里有重复的元素,也很容易在某个索引中出错。
for i in 0:(length(a)÷3-1)
triplet = a[3i+1:3i+3]
println("sum of $triplet -> $(sum(triplet))")
end
此外,我们还可以看到,使用视图的函数分配的内存要少得多,运行速度也要快得多。
为此,我们将使用宏@btime
,它可以通过多次运行函数并取平均值来显示函数的执行时间和分配的内存。
(我们删除了函数向控制台的输出,以避免在多次调用函数时堵塞控制台)。
using BenchmarkTools
function tripletssum_subarray(v)
for i in 0:(length(v)÷3-1)
triplet = v[3i+1:3i+3]
end
end
function tripletssum_view(v)
for i in 0:(length(v)÷3-1)
triplet = @view v[3i+1:3i+3]
end
end
a = repeat(1:10000,inner=3)
println(@btime tripletssum_subarray(a))
println(@btime tripletssum_view(a))
@views¶
让我们看看下面的示例
Pkg.add("LinearAlgebra")
using LinearAlgebra
@btime dot( a[1:end÷2], a[end÷2+1:end])
我们似乎知道如何改进这段代码了:
try
# ОСНОВНОЙ КОД
#------------------------------------------------------
@btime dot(@view a[1:end÷2], @view a[end÷2+1:end])
#------------------------------------------------------
# ОБРАБОТКА ИСКЛЮЧЕНИЯ
catch e
io = IOBuffer();
showerror(io, e)
error_msg = String(take!(io))
end
@btime dot(@view(a[1:end÷2]) ,@view(a[end÷2+1:end]))
但为了避免每次切分都写@view
,我们可以使用宏@views
@btime @views dot((a[1:end÷2]), (a[end÷2+1:end]))
@views
插入到函数定义之前,这样函数内部的分片将使用视图执行。
@views function tripletssum_views(v)
for i in 0:(length(v)÷3-1)
triplet = v[3i+1:3i+3]
end
end
a = repeat(1:10000,inner=3)
println(@btime tripletssum_views(a))
a = rand(1000)
println(@allocated sum(a))
println(@allocated sum(a[1:end]))
println(@allocated sum(copy(a[1:end])))
println(@allocated sum(@view a[1:end]))
import Pkg; Pkg.add(["OrdinaryDiffEq","Plots"])
using OrdinaryDiffEq
using Plots
gr()
function lotka(du, u, p, t)
du[1] = p[1] * u[1] - p[2] * u[1] * u[2]
du[2] = p[4] * u[1] * u[2] - p[3] * u[2]
end
α = 1; β = 0.01; γ = 1; δ = 0.02;
p = [α, β, γ, δ]
tspan = (0.0, 6.5)
u0 = [50; 50]
prob = ODEProblem(lotka, u0, tspan, p)
sol = solve(prob,saveat=0.001)
$x(t)$ 和$y(t)$ 变量的时间相关性将用于绘制图形。
plot(sol)
但如果我们要绘制$y(x)$ 的依赖关系,就必须使用切片sol[1,:]
和sol[2,:]
。这让我们想起了上述问题。
@btime plot(sol[1,:],sol[2,:])
现在我们可以使用视图来解决这个问题:
@btime @views plot(sol[1,:],sol[2,:])
结论¶
在了解了视图的概念之后,我们已经考虑了无需对代码进行重大修改即可提高函数性能的实用方法。