在 Julia 中,有没有任何方法可以加速“点积”和“hcat”操作?

In Julia, are there any methods to accelerate the 'dot product' and 'hcat' operations?

提问人:Satoshi 提问时间:8/12/2023 最后编辑:Satoshi 更新时间:8/13/2023 访问量:67

问:

我在 Julia 中尝试了一个简单的矩阵计算,但它非常大。它运行得很慢。有什么方法可以加快速度吗?

a = ones(1000000,200)
b = ones(1000000,1).*2

@time begin
hcat((a.*(b.^2)), (a.*b),a)
end

此外,我还有一个后续问题。累积计算所需的时间比单独处理每个零件的时间要长得多。换句话说,前面提到的代码比我将要展示的代码慢。为什么会这样?

@time begin
temp1 = (b.^2)
end

@time begin
temp2 = a.*temp1
end

@time begin
temp3 = (a.*b)
end

@time begin
result = hcat(temp2,temp3,a)
end

我不想像@threads那样使用并行计算。

性能 数学 矩阵 统计 Julia

评论


答:

2赞 Przemyslaw Szufel 8/12/2023 #1
  1. 你做错了基准。 正在测量编译时间。请改用 @btime from 。请参阅讨论 内置 @time 宏与基准模块中的宏之间的区别@timeBenchmarkingTools

  2. 代码的主要问题是分配。你可以在很大程度上避免它们,而是使用。BlockArrays

  3. @simd可以加快单线程代码的速度。但是,无论是否有帮助 - SIMDed 代码需要没有分支,并且广播等一些操作使用 SIMD。有关讨论,请参阅 https://www.youtube.com/watch?v=MUGQnq7mkSs

  4. 乘法比^2

下面是一些说明性的例子。

对原始代码进行基准测试:

julia> @btime begin
       hcat(($a.*($b.^2)), ($a.*$b),$a)
       end
  3.114 s (32 allocations: 7.45 GiB)
1000000×600 Matrix{Float64}:
 4.0  4.0  4.0  4.0  4.0  …  1.0  1.0  1.0  1.0  1.0
 ...

避免额外分配的代码速度是原来的 3 倍:using BlockMatrices

julia> @btime begin
          mortar(( $a.* ($b .* $b), ($a .* $b), $a))
       end
  1.186 s (13 allocations: 2.98 GiB)
1×3-blocked 1000000×600 BlockMatrix{Float64}:
 4.0  4.0  4.0  4.0  4.0  …  1.0  1.0  1.0  1.0  1.0
 ...

您可以考虑编写一个 SIMD 代码(这也会产生一个常规矩阵,但代码更复杂):

function calc(a,b)
  res = Matrix{Float64}(undef, size(a,1), size(a,2)*3)
  J = size(a,2)
  @view(res[:, 2J+1:3J]) .= a
  for j in 1:J 
      @inbounds @simd for i in 1:size(a,1)
         res[i, J+j] = a[i,j]*b[i,1]
      end
  end
  for j in 1:J 
      @inbounds @simd for i in 1:size(a,1)
         res[i, j] = res[i, J+j]*b[i,1]
      end
  end       
  res
end

Simd 表现得非常好,并生成了一个常规矩阵:

julia> @btime calc($a,$b)
  1.089 s (2 allocations: 4.47 GiB)
1000000×600 Matrix{Float64}:
 4.0  4.0  4.0  4.0  4.0  …  1.0  1.0  1.0  1.0  1.0
0赞 AboAmmar 8/13/2023 #2

超过 50% 的时间已经花在分配一个巨大的 4.47 GiB 数组上,但我们仍然可以使用 LoopVectorization.jl 为剩余的 50% 挤出一些性能提升。

using LoopVectorization

function hcat3(a, b)
    I, J = size(a)
    res = Matrix{Float64}(undef, I, 3J)
    @turbo res[:,    1: J] .= a .* (b .* b)
    @turbo res[:,  J+1:2J] .= a .* b
    @turbo res[:, 2J+1:3J] .= a
end
@btime hcat3($a, $b) # 1.157 s (2 allocations: 4.47 GiB)
@btime calc($a, $b)  # 1.323 s (2 allocations: 4.47 GiB)
@btime hcat(($a.*($b.^2)), ($a.*$b),$a) # 1.883 s (6 allocations: 7.45 GiB)