Jor*_*tes 5 performance struct types julia
我对 Julia 还很陌生,在尝试做某些事情时,我仍然对哪种风格更好有些怀疑……例如,我对使用抽象类型与定义联合的性能或风格差异有很多疑问。
一个例子:假设我们想要实现几种类型的单位(生物、骑士……),它们应该共享大部分(如果不是全部)属性和大部分(如果不是全部)方法。我看到提供结构的两种选择:第一,可以声明一个抽象类型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
[...]
Run Code Online (Sandbox Code Playgroud)
或者,可以定义类型的联合并为该联合创建方法。它看起来像这样:
mutable struct Knight
id :: Int
[...]
end
mutable struct Peasant
id :: Int
[...]
end
const Unit = Union{Knight,Peasant}
id(u::Unit) = u.id
[...]
Run Code Online (Sandbox Code Playgroud)
我理解这两种方法之间的一些概念差异,并认为第一种方法更具可扩展性。然而,我对性能有很多疑问。AbstractUnit例如,在运行时的内存分配方面,创建联合类型的数组与创建联合类型的数组会有多糟糕?
谢谢!
绝对在方法的参数类型注释中使用AbstractUnit而不是使用。Unit实际上,联合也是抽象类型,但正如您所指出的,您不能向其中添加新类型。在任何一种情况下,该方法都会被编译为每个具体类型的专门化,例如Knight或Peasant,因此您的方法的性能不会有所不同。
至于数组的元素类型参数,有isbits Union optimization,但顾名思义,它只有在 Union 中的所有类型都是isbitstypes (无指针,不可变)时才有效。您的结构是可变的,因此已经不适用。请注意,当实例直接存储在数组中时,内存访问速度会更快,并且元素类型参数 ( Tin Vector{T}) 必须是具体的isbitstype才能允许这样做。当元素类型参数是抽象或可变时,通常数组仅直接存储指向实际实例的指针,因为多个或可变的具体类型可能具有未知且变化的内存大小。如果抽象类型是isbitsUnion,则实例可以直接存储在数组中:为每个元素分配足够的内存来包含 Union 中最大的具体类型以及指定其类型的每个元素的标记字节。一个字节只有 256 个值,因此大概这只适用于最多 256 个具体类型的联合。
使用Vector{Unit}over 的另一种可能的优化Vector{AbstractUnit}是联合分割类型不稳定性。我真的无法制作一个比链接博客更好地解释它的示例方法,所以我只会给出简短的版本。当 Julia 的编译器根本无法推断方法中变量的类型(::Any注释@code_warntype)时,涉及该变量的内部方法调用必须在运行时进行类型检查和分派(选择专门化),这可能会花费大量时间。但是,如果 Julia 的编译器可以将变量推断为几个具体类型(实际上最多 4 个)的 Union,则可以使用每种类型的条件分支来消除大多数类型检查并在编译时进行分派。Vector{AbstractUnit}可以包含任意数量的类型<: AbstractUnit,因此编译器不能使用联合分割。Vector{Unit}然而,让编译器知道元素必须是Knightor Peasant,这允许联合分割。
PS 这通常是初学者感到困惑的一个原因,虽然Unit是一个抽象类型,但Vector{Unit}它是一个具体类型,只是带有一个抽象类型参数。毕竟,它可以有实例,所有数组都直接包含指向Knight或Peasant实例的指针。