我對 Julia 很陌生,在嘗試做某些事情時,我仍然對哪種風格更好有一些疑問......例如,我對使用抽象型別與定義聯合的性能或風格差異有很多疑問。
一個例子:假設我們想要實作幾種型別的單位(Mob、Knight、...),它們應該共享大部分(如果不是全部)屬性和大部分(如果不是全部)方法。我看到兩種提供結構的選項:首先,可以宣告一個抽象型別AbstractUnit,其他型別從該抽象型別派生,然后為抽象型別創建方法。它看起來像這樣:
abstract type AbstractUnit end
mutable struct Knight <: AbstractUnit
id :: Int
[...]
end
mutable struct Peasant <: AbstractUnit
id :: Int
[...]
end
id(u::T) where T <: AbstractUnit = u.id
[...]
或者,可以定義型別的聯合并為聯合創建方法。它看起來像這樣:
mutable struct Knight
id :: Int
[...]
end
mutable struct Peasant
id :: Int
[...]
end
const Unit = Union{Knight,Peasant}
id(u::Unit) = u.id
[...]
我理解這兩種方法之間的一些概念差異,并認為第一種方法更具擴展性。但是,我對性能有很多疑問。例如,就AbstractUnit運行時的記憶體分配而言,創建聯合型別的vs 陣列會有多糟糕?
謝謝!
uj5u.com熱心網友回復:
絕對在方法的引數型別注釋中使用AbstractUnit而不是Unit。實際上,聯合也是抽象型別,但是正如您所指出的,您不能向其中添加新型別。在任何一種情況下,該方法都會被編譯為每個具體型別的特化,例如Knight或Peasant,因此您的方法的性能不會有所不同。
至于陣列的元素型別,有isbits Union optimization,但顧名思義,它只有在您的 Union 中isbitstype的所有型別都是s (沒有指標,不可變)時才有效。您的結構是可變的,因此已經不適用。看,當實體直接存盤在陣列中時,記憶體訪問速度更快;元素型別引數(Tin Vector{T})必須是具體的isbitstype以允許這樣做。當元素型別引數是抽象的時,通常陣列只直接存盤指向實際實體的指標,因為多個具體型別可能具有未知且變化的記憶體大小。如果抽象型別是isbits盡管如此,實體可以直接存盤在 Array 中:為每個元素分配足夠的記憶體以包含 Union 中最大的具體型別以及指定型別的每個元素的標記位元組。一個位元組只有 256 個值,因此大概這僅適用于型別較少的聯合。
使用Vector{Unit}over 的另一種可能優化Vector{AbstractUnit}是Union-splitting。我真的無法制作一個示例方法來比鏈接的博客更好地解釋它,所以我只會給出簡短的版本。通常,當 Julia 的編譯器無法推斷方法中變數的具體型別時(又名型別不穩定),涉及變數的內部方法呼叫必須在運行時進行型別檢查和分派(選擇特化),這可能會花費大量時間. 但是,如果 Julia 的編譯器可以將變數推斷為幾種型別的聯合(實際上,最多 4 個),則可以使用每種型別的條件分支來消除大多數型別檢查并在編譯時進行分派。Vector{AbstractUnit}可以包含任意數量的型別<: AbstractUnit,所以編譯器不能使用聯合拆分。Vector{Unit}但是讓編譯器知道元素必須是Knightor Peasant,這允許聯合拆分。
PS 這對于初學者來說常常是一個困惑的來源,但是雖然Unit是抽象型別,Vector{Unit}是具體型別,只是帶有一個抽象型別引數。畢竟,它明確地描述了一個直接包含指向Knight或Peasant實體的指標的陣列。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/377313.html
上一篇:在java中,為什么它很慢?
