Julia ---- 廣播變量的性能問題

翻譯內容

在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


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章