在朱莉娅中最佳地传递固定尺寸阵列的尺寸

时间:2016-07-18 19:22:57

标签: performance julia fixed-size-types

我想编写一个以矩阵作为输入的函数。这是一个复杂项目中的频繁低级调用,因此尽可能快地使这个功能具有潜在的严重性能影响。因为速度对我来说非常重要,所以我使用FixedSizeArrays中的类型,因为我知道这将节省内存使用量。但我经常知道输入矩阵的某些属性,而且我不确定我是否能最佳地利用它。

这是一个简单的例子。想象一下,我希望尽可能快地完成以下功能:

using FixedSizeArrays

function foo( input::Mat )
# NB: Mat is the FixedSizeArrays matrix type
  return 2 * input
end

显然这是一个微不足道的例子,但这不是重点。关键是我对矩阵input的维度有所了解:它总是只有两列,我总是可以在运行时指定行数。这似乎是可以传递给编译器以使我的代码更快的信息。我可以将它作为参数传递,以某种方式定义input的大小吗?这是一个不起作用的例子,但应该让你知道我想要做什么。

function bar( int::N, thismat::Mat{N,2,Float64} )
  return 2 * thismat
end

我能做到这样吗?如果可以的话,这会发挥作用吗?也许FixedSizeArrays已经做了所有可以完成的事情。谢谢你的想法!

1 个答案:

答案 0 :(得分:8)

固定大小的数组已经专门用于大小。当您的案例中的行数N发生变化时,这些数组不适用。您注意到的任何性能问题都可能是因为过度专业化

让我更具体一点。

Julia编译器能够通过对参数类型的积极专业化来实现零成本抽象。所以一般情况下(也就是说,除了少数专业化过于昂贵或明确禁用的情况),如果使用两个不同类型的签名调用函数,则将编译此函数的两个版本。

由于Mat的大小是其类型的一部分,这意味着将为Mat的每个可能大小编译版本。所以你所寻求的专业化已经完成了。

然而,专业化不是免费的。它有两个成本:

  • 第一次在特定签名上调用函数时,将分配内存并且编译器必须运行。
  • 当无法推断其类型的参数传递给函数时,存在"类型不稳定",并且需要动态分派。动态调度涉及运行时查找。

因此,如果您的矩阵大小为(2, N),其中N发生变化且在编译时未知,则会产生动态调度的性能成本。这种性能成本可以通过使用功能障碍技术来限制:我们只为每个类型不稳定的呼叫产生一次成本,因此限制此类呼叫的数量可以提高性能。

但是,更能提高性能的是完全避免这种动态调度。可以构造一个仅对该类型中的列数进行编码的数组类型,并将行数作为运行时的字段。也就是说,您的性能问题可能是由于过度专业化造成的,您需要创建类型以减少专业化。

找到适当的平衡对于从应用程序中挤出尽可能多的性能至关重要。专门研究数组的大小实际上非常有用 - 例如,即使C和C ++代码也倾向于将数组大小作为运行时参数传递,而不是专门针对特定的数组大小。这并不贵。在更多情况下,FixedSizeArrays.jl不会提高性能,而是会伤害它。当然有一些情况会有所帮助 - 但你的可能不是其中之一。

在你的情况下,为了获得最佳性能,我怀疑这样的类型会是最快的:

immutable TwoColumnMatrix{T, BaseType} <: AbstractArray{T, 2}
    height::Int
    base::BaseType
end

function TwoColumnMatrix(A::Matrix)
    size(A, 2) == 2 || throw(ArgumentError("must be two columns"))
    TwoColumnMatrix{eltype(A), typeof(A)}(size(A, 1), A)
end

Base.@propagate_inbounds function getindex(M::TwoColumnMatrix, n::Int)
    M.base[n]
end

size(M::TwoColumnMatrix) = (M.height, 2)

您可能需要定义其他方法以获得最佳性能,并且一如既往地定义基准。可能包装器的开销不值得编译器了解尺寸。