在Julia中使用NTuple参数类型与使用抽象向量的区别

4

我目前正致力于开发支持二进制操作的解析器。代表二进制操作的想法是使用这种结构

struct Binary{T <: BinaryKind} <: Operation
    xs::Vector{Union{Operation, Atom}}
end

OperationAtom 都是抽象类型时,阅读 Julia 文档中的性能提示后,我意识到表示这个结构更高效的方法是:

struct Binary{T <: BinaryKind, D <: Tuple} <: Operation
    xs::D
end

但是,由于我可以使用嵌套二进制运算,我相信可能会出现非常长的类型定义情况,这甚至可能比使用抽象类型更糟糕。有没有方法可以改善这种情况?


请考虑是否真的需要限制参数 D。除非限制有特定目的,否则您可以使用 struct Binary{T <: BinaryKind, D} <: Operation - DNF
该限制没有特定目的,但由于它将始终是一个元组,我最终会得到长嵌套类型。避免指定元组内容对于类型推断来说是否更好? - MPaga
那么,你不能只是不指定它,然后完全放弃嵌套元组的类型吗?我认为这对于类型推断没有任何影响。 - DNF
1个回答

1

我认为(评论区字数不够,所以我把它作为答案)在这种情况下,可能最好是类型不稳定的。请注意,这正是Julia本身所做的:

julia> x = :(1+2*(3+4))
:(1 + 2 * (3 + 4))

julia> dump(x)
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol *
        2: Int64 2
        3: Expr
          head: Symbol call
          args: Array{Any}((3,))
            1: Symbol +
            2: Int64 3
            3: Int64 4

当然,Julia有着更加丰富的语法,但即使在你的简单情况下也要考虑以下内容。如果您编译代码的某个部分一次,然后以这种或其他形式运行它多次,您将获得类型稳定性的好处。
现在我假设您所写的内容实际上大多只会被评估一次。如果您使表达式完全类型稳定,每次都必须付出代价(假设表达式发生变化):
1. 编译它的成本(昂贵)。 2. 运行它的成本(相对便宜)。
如果您的代码是类型不稳定的,您只需要支付一次编译成本。的确,运行速度会稍慢,但总体来说,这种方式可能更好。
另一方面 - 如果您希望只定义一次表达式,然后多次运行它,那么可能更好的方法是使用元编程:
1. 仅处理您的表达式一次并生成将评估您的表达式的Julia代码。 2. 然后Julia将编译生成的代码一次。 3. 在完成步骤1和2之后执行它可以获得最大的执行性能。

对于你的问题,一种半成品解决方案是使用以下数据结构:

struct Binary{T <: BinaryKind, S} <: Operation
    xs::Vector{S}
end

这样,如果S是一个具体类型或者是由少数几个具体类型组成的小型联合类型,你的代码将会是类型稳定的;否则就是类型不稳定的(我预计此时你可以尝试让其余代码以一种方式生成xs,使得它的eltype是具体类型或者是由少数几个具体类型组成的小型联合类型)。如有更多问题,请在评论区留言,我可以详细解答。

谢谢你的回答。对于某些操作,我需要更改表达式,因此最好的解决方案可能是使用您建议的半措施。话虽如此,有些情况下我不会更改表达式,也许使用基于元编程的方法会更好。我可能会考虑一种方式来保持类似于现在所拥有的数据结构,但使用Julia的表达式语法,以便在类型稳定性更可取时使用。 - MPaga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接