我想计算一个二次形式:x' Q y
,使用Julia编程语言。
以下是在不同情况下最有效的计算方式:
- 没有假设。
Q
是对称的。x
和y
相同 (x = y
)。- 同时满足
Q
对称和x = y
。
我知道Julia有dot()
函数。但我想知道它是否比BLAS调用更快。
我想计算一个二次形式:x' Q y
,使用Julia编程语言。
以下是在不同情况下最有效的计算方式:
Q
是对称的。x
和 y
相同 (x = y
)。Q
对称和 x = y
。我知道Julia有dot()
函数。但我想知道它是否比BLAS调用更快。
using MKL
,即可通过MKL.jl包切换到MKL后端。y'*X*y
中会发生,在查看源代码时,在对三个参数dot
的调用中的某些步骤上除外,但是在Julia 1.4及以后版本中,dot(x :: AbstractVector,A :: AbstractMatrix,y :: AbstractVector)
具有非分配的纯Julia实现。 - cbkJulia的LinearAlgebra标准库有原生实现的三参数dot
,还有一个专门针对对称/共轭矩阵的版本。您可以在这里查看源代码和 这里。
您可以使用BenchmarkTools.@btime
或BenchmarkTools.@ballocated
来确认它们不会分配任何内存(请记得使用$
内插变量)。这些函数利用了矩阵的对称性,但是从源代码中看,除了节省一些数组查找之外,我不认为x == y
能带来任何严重的加速。
编辑:要比较BLAS版本和原生版本的执行速度,您可以执行以下操作:
1.7.0> using BenchmarkTools, LinearAlgebra
1.7.0> X = rand(100,100); y=rand(100);
1.7.0> @btime $y' * $X * $y
42.800 μs (1 allocation: 896 bytes)
1213.5489200642382
1.7.0> @btime dot($y, $X, $y)
1.540 μs (0 allocations: 0 bytes)
1213.548920064238
这对原生版本来说是一个重大的胜利。然而,当处理更大的矩阵时情况会发生改变:
1.7.0> X = rand(10000,10000); y=rand(10000);
1.7.0> @btime $y' * $X * $y
33.790 ms (2 allocations: 78.17 KiB)
1.2507105095988091e7
1.7.0> @btime dot($y, $X, $y)
44.849 ms (0 allocations: 0 bytes)
1.2507105095988117e7
可能是因为BLAS使用了线程,而dot
没有实现多线程。还存在一些浮点数差异。
@tturbo
而不是@simd
的@dot
版本?我很想看看LoopVectorization
在这里的表现如何。 - Oscar Smith@simd
替换为 @turbo
可以略微提高速度,而 @tturbo
与 @turbo
看起来相同。这些宏放置在内循环中。我认为将外循环线程化会更有益。 - DNFiszero
检查(以允许 @tturbo
在外部循环中运行)后,我得到了一个与 BLAS 在大规模情况下一样快的结果,并且比小规模情况下的 dot
快 3 倍。 - Oscar SmithSymmetric
包装器来提高性能(然后调用不同的方法):julia> a = rand(10000); b = rand(10000);
julia> x = rand(10000, 10000); x = (x + x') / 2;
julia> y = Symmetric(x);
julia> @btime dot($a, $x, $b);
47.000 ms (0 allocations: 0 bytes)
julia> @btime dot($a, $y, $b);
27.392 ms (0 allocations: 0 bytes)
x
与y
相同,请参见https://discourse.julialang.org/t/most-efficient-way-to-compute-a-quadratic-matrix-form/66606以了解选项的讨论(但通常dot
仍然比较快)。dot
产生一个标量。 - Bogumił Kamińskiy'*X*y
不同,dot(y,X,y)
似乎根本不分配内存。您可以使用 BenchmarkTools.@btime
进行检查。 - DNF@less dot(a, X, b)
显示了一种本地实现,而@btime
显示零分配。我会再深入研究一下,然后发表答案。 - DNF链式乘法也非常慢,因为它不知道有更好的算法。
julia> # a, b, x are the same as in Bogumił's answer
julia> @btime dot($a, $x, $b);
82.305 ms (0 allocations: 0 bytes)
julia> f(a, x, b) = @tullio r := a[i] * x[i,j] * b[j]
f (generic function with 1 method)
julia> @btime f($a, $x, $b);
80.430 ms (1 allocation: 16 bytes)
添加LoopVectorization.jl可能是值得的:
julia> using LoopVectorization
julia> f3(a, x, b) = @tullio r := a[i] * x[i,j] * b[j]
f3 (generic function with 1 method)
julia> @btime f3($a, $x, $b);
73.239 ms (1 allocation: 16 bytes)
但是我不知道该如何处理对称情况。
julia> @btime dot($a, $(Symmetric(x)), $b);
42.896 ms (0 allocations: 0 bytes)
Tulio.jl
添加的一个特性。 - Eric JohnsonSymmetric
要执行的优化实现表示为一个einsum表达式,然后再使用Tullio。 - phipsgabler