翻譯內容
在Julia中廣播是一種編寫向量化代碼(類似Matlab)的方法,它是一種高效和明確的方法。除了代碼執行速度更快以外,顯式矢量化也是一個顯著的優點。但也是有限制的,對複雜函數進行矢量化可能需要相當多的練習才能熟練使用,但通常可讀性較差。
Julia中使用點廣播(矢量化)。如果希望調用函數時使用向量(對向量的每個元素應用相同的函數),只需在函數調用上加一個點。例如,值向量的正弦變成sin.[1.1,0.3,2.3]);注意sin和第一個括號之間的額外點。
在Julia v0.7/1.0中,廣播的工作方式發生了一些變化。(請參閱Extensible broadcast fusion。)它現在創建一系列Broadcasted對象,這些對象融合在一起,然後最終實現函數以給出最終結果。例如,
r = sqrt(sum(x.^2 .+ y.^2))
在內部,它被重寫(“lowered”)爲
r = sqrt(sum(materialize(broadcasted(+, broadcasted(^, x, 2), broadcasted(^, y, 2)))))
(這在細節上不太準確,因爲平方的實現稍有不同。)請注意廣播調用的代碼層次結構,它們封裝在要具體化的調用中的。這就是廣播融合的魔力所在,這也使Julia能夠構造性能良好的代碼。廣播調用創建一組嵌套的廣播對象,這些對象包含(延遲計算的)矢量表達式,實現的調用就是由此創建最終矢量。
大多數時候,我們想要這種自動魔法實現,但有時候不需要。
考慮上面sum函數的情況;將在內存中爲計算x.^2+y.^2分配一個向量,如果x和y很大,則將不必要地爲中間值分配大量內存。既然sum函數不會同時使用所有的值,我們就不能懶洋洋地把x.^2+y.^2作爲一個個單獨的數字來計算,然後一個一個地把它們輸入sum函數嗎?例如,可以這麼做
const acc = 0.0
for i = eachindex(x, y)
acc += x[i]^2 + y[i]^2
end
r = sqrt(acc)
在這種情況下,使用顯式for循環來試圖避免的上面提到的分配大量內容的情況(這裏有一個問題,我們爲什麼要費心使用廣播?)。我們是否可以從廣播中提取延遲表示,而不具體化中間結果嗎?
答案是肯定的,但不幸的是,這還不是Julia base庫的一部分。下面的代碼爲我們提供了一個惰性宏,它使我們能夠訪問廣播創建的延遲加載的表達式,並在上下文中其他的代碼裏顯式地使用它。
@inline _lazy(x) = x[1] # unwrap the tuple
@inline Broadcast.broadcasted(::typeof(_lazy), x) = (x,) # 使用tuple包裝廣播對象來避免實時具體化,以達到延遲的效果
macro lazy(x)
return esc(:(_lazy(_lazy.($x))))
end
現在我們可以比較延遲加載的版本和實時具體化版本。
julia> using BenchmarkTools
julia> x = rand(1_000_000) ; y = rand(1_000_000) ;
julia> @btime sqrt(sum(x.^2 .+ y.^2)) # normal eager evaluation
2.837 ms (16 allocations: 7.63 MiB)
816.7514405417339
julia> @btime sqrt(sum(@lazy x.^2 .+ y.^2)) # lazy broadcasted evaluation
1.075 ms (12 allocations: 208 bytes)
816.7514405417412
注意內存消耗:正常版本爲7.63mib,而延遲計算的版本爲208字節。類似地,延遲計算版本的速度要快得多(儘管這在很大程度上取決於使用的向量的大小)。在這兩種情況下有一個稍微不同的結果,因爲Julia sum函數對向量和迭代器使用稍微不同的算法。
爲什麼延遲計算的版本不是默認版本呢?這裏有一個警告:一旦你做了延遲計算的評估,性能就變得更加依賴於具體的問題了,使用延遲計算可以變得更快(在本例中),但同樣,在其他地方,它也可以變得更慢。這時候BenchmarkTools.jl是您的朋友了,你可以使用它調試具體的性能!
本地驗證了下:
使用版本是Julia-1.0.5
using BenchmarkTools
@inline _lazy(x) = x[1] # unwrap the tuple
@inline Broadcast.broadcasted(::typeof(_lazy), x) = (x,) # 使用tuple包裝廣播對象來避免實時具體化,以達到延遲的效果
macro lazy(x)
return esc(:(_lazy(_lazy.($x))))
end
x = rand(1_000_000) ; y = rand(1_000_000) ;
@btime sqrt(sum(x.^2 .+ y.^2)) # 正常的實時計算
#816.3678796598351
@btime sqrt(sum(@lazy x.^2 .+ y.^2)) # 使用延遲計算
#816.3678796598531