视图作为提高代码性能的一种方法
此脚本讨论了视图的使用,这是一种允许访问数组元素而不创建它们的副本的机制。
主题将涵盖:
-切片副本(切片)和视图(视图)之间的区别
-宏的使用 @view 和 @views 和他们的差异。
为了测试使用视图的有效性,我们将连接基准库。
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 它与上述关于"熟悉的界面"的声明不符,因为我们无法使用关键字 end.
您可以使用宏来解决此问题。 @view:
a = repeat(1:10,inner=3)
b = @view a[end-3:end]
b .= 0
a'
但问题可能会出现:为什么我们需要额外的变量,当你可以做
'茱莉亚 a=重复(1:10,内=3) a[结束-3,3].= 0 该
对此的答案可以制定如下:
视图是有效利用资源和保持代码可读性的组合。
假设有一个任务输出并计算三元组元素的总和。
``'茱莉亚
对于i in0:(长度(a)≈3-1)
println("总和 $(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))
@意见
考虑以下示例
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
错误说我们不正确地使用宏。 @view.
虽然好像是我们的表情 a[1:end÷2] 对应于表达式 A[...].
问题是我们[错误地使用了宏](https://engee.com/helpcenter/stable/ru/julia/manual/metaprogramming.html#вызов-макроса )。
为了解决这种情况,我们将要应用表示的向量放在括号中。:
@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)
绘制图形时,将使用时间因变量。 和 .
plot(sol)
但是如果我们要绘制一个依赖 然后我们将不得不使用切片。 sol[1,:] 和 sol[2,:]. 这让我们想起了前面提到的问题。
@btime plot(sol[1,:],sol[2,:])
我们现在可以用表示来解决:
@btime @views plot(sol[1,:],sol[2,:])
结论
在熟悉了表示的概念之后,考虑了改进不需要对代码进行任何重大更改的函数性能的实用方法。


