Julia 是否需要矢量化来加速计算?

Does Julia need vectorization to speed up computation?

在Python中,通常建议对代码进行向量化,以加快计算速度。例如,如果你想计算两个向量的内积,比如 ab,通常是

代码A

c = np.dot(a, b)

优于

代码 B

c = 0
for i in range(len(a)):
    c += a[i] * b[i]

但在 Julia 中,有时向量化似乎并没有那么大的帮助。我将 '*dot 视为矢量化版本,将显式 for 循环视为非矢量化版本,并得到以下结果。

using Random
using LinearAlgebra

len = 1000000
rng1 = MersenneTwister(1234)
a = rand(rng1, len)
rng2 = MersenneTwister(12345)
b = rand(rng2, len)


function inner_product(a, b)
    result = 0
    for i in 1: length(a) 
        result += a[i] * b[i] 
    end
    return result
end


@time a' * b
@time dot(a, b)
@time inner_product(a, b);
  0.013442 seconds (56.76 k allocations: 3.167 MiB)
  0.003484 seconds (106 allocations: 6.688 KiB)
  0.008489 seconds (17.52 k allocations: 976.752 KiB)

(我知道使用 BenchmarkTools.jl 是衡量性能的更标准方法。)

从输出来看,dotfor'* 运行得更快,这与假设相矛盾。

所以我的问题是,

  1. Julia 是否需要(或有时需要)向量化来加速计算?
  2. 如果是,那么什么时候使用矢量化以及哪种使用方式更好(考虑 dot'*)?
  3. 如果不是,那么Julia和Python在向量化和非向量化代码的机制上有什么区别?

您没有正确制定基准并且您的功能实现不是最理想的。

julia> using BenchmarkTools                   
                                              
julia> @btime $a' * $b                        
  429.400 μs (0 allocations: 0 bytes)         
249985.3680190253                             
                                              
julia> @btime dot($a,$b)                      
  426.299 μs (0 allocations: 0 bytes)         
249985.3680190253                             
                                              
julia> @btime inner_product($a, $b)           
  970.500 μs (0 allocations: 0 bytes)         
249985.36801903677                            

正确的实现方式:

function inner_product_correct(a, b)
    result = 0.0 #use the same type as elements in the args
    @simd for i in 1: length(a)
        @inbounds result += a[i] * b[i]
    end
    return result
end
julia> @btime inner_product_correct($a, $b)
  530.499 μs (0 allocations: 0 bytes)
249985.36801902478

仍然存在差异(但不太重要),因为 dot 使用的是优化的多线程 BLAS 实现。你可以(按照 Bogumil 的评论集 OPENBLAS_NUM_THREADS=1 然后你会发现 BLAS 函数的时间将与 Julia 实现相同。

另请注意,使用浮点数在很多方面都很棘手:

julia> inner_product_correct(a, b)==dot(a,b)
false

julia> inner_product_correct(a, b) ≈ dot(a,b)
true 

最后,在 Julia 中,决定是使用向量化还是自己编写循环取决于你 - 没有性能损失(只要你编写类型稳定的代码并使用 @simd@inbounds 在需要的地方)。但是,在您的代码中,您没有测试矢量化,而是将调用 BLAS 与自己编写循环进行比较。这是必须阅读的内容,以了解正在发生的事情 https://docs.julialang.org/en/v1/manual/performance-tips/

让我添加我的实践经验作为评论(对于标准评论来说太长了):

does Julia need (or sometimes need) vectorization to speed up computation?

Julia 不需要矢量化,因为 Python(请参阅 Przemysław 的回答),但在实践中,如果您有一个编写良好的矢量化函数(如 dot),那么尽可能使用它, 自己编写高性能函数有时可能会很棘手(人们可能花了几天时间来优化 dot,尤其是优化使用多线程)。

If it does, then when to use vectorization and which is the better way to use (consider dot and '*)?

当您使用矢量化代码时,这完全取决于您要使用的功能的实现。在这种情况下 dot(a, b)a' * b 与您在这种情况下编写 @edit a' * b 时完全相同:

*(u::AdjointAbsVec{<:Number}, v::AbstractVector{<:Number}) = dot(u.parent, v)

你看也是一样的

If it does not, then what is the difference between Julia and Python in terms of the mechanism of vectorized and non-vectorized codes?

Julia 是一种编译型语言,而 Python 是一种解释型语言。在某些情况下 Python 解释器可以提供快速的执行速度,但在其他情况下目前还做不到(但不代表以后不会改进)。特别是矢量化函数(如您问题中的 dot )很可能是用某种编译语言编写的,因此 Julia 和 Python 在典型情况下不会有太大差异,因为它们只是调用此编译函数。但是,当您使用循环(非矢量化代码)时,目前 Python 将比 Julia 慢。