优化代码

使用宏 @inbounds 加速循环

@inbounds 以牺牲程序的安全性来大幅度加快循环的执行效率。 在对数组结构循环的时候,使用该宏需要确保程序的正确性。

节约空间

Julia提供了很多inplace的函数,合理地使用能够避免分配不必要的空间,从而减少gc的调用且节约了分配空间的时间。比如在数值上的共轭梯度法,其中的矩阵乘法可以使用 A_mul_B! 而不是 *(A,x), 这样避免了每次进入循环的时候分配新的空间

julia> A = rand(5000,5000); x = rand(5000); y = rand(5000);
julia> @time y = A*x;
elapsed time: 0.016998118 seconds (40168 bytes allocated)
julia> @time A_mul_B!(y,A,x);
elapsed time: 0.012711399 seconds (80 bytes allocated)

手动优化循环(manual loop unrolling)

手动地将循环平铺开能削减循环带来的overhead, 因为每次进入循环都会进行 end-of-loop 检测, 手动增加一些代码会加速循环的运行。缺点是会产生更多的代码,不够简洁,尤其是在循环内部调用inline函数,反而有可能降低性能。一个比较好的例子是 Julia 的sum函数。这里只做个简单的对比

function simple_sum(a::AbstractArray, first::Int, last::Int)
    b = a[first];
    for i = first + 1 : last
        @inbounds b += a[i]
    end
    return b
end

function unroll_sum(a::AbstractArray, first::Int, last::Int)
    @inbounds if ifirst + 6 >= ilast # length(a) < 8
        i = ifirst
        s =  a[i] + a[i+1]
        i = i+1
        while i < ilast
            s +=a[i+=1]
        end
        return s

    else # length(a) >= 8, manual unrolling
        @inbounds s1 = a[ifirst] +  a[ifirst + 4]
        @inbounds s2 = a[ifirst + 1] + a[ifirst + 5]
        @inbounds s3 = a[ifirst + 2] +  a[ifirst + 6]
        @inbounds s4 = a[ifirst + 3] +  a[ifirst + 7]
        i = ifirst + 8
        il = ilast - 3
        while i <= il
          @inbounds  s1 +=  a[i]
          @inbounds  s2 += a[i+1]
          @inbounds  s3 += a[i+2]
          @inbounds  s4 += a[i+3]
            i += 4
        end
        while i <= ilast
          @inbounds  s1 += a[i]
            i += 1
        end
        return s1 + s2 + s3 + s4
    end
end

运行结果如下

julia>rand_arr = rand(500000);
julia>@time @inbounds ret_1 = simple_sum(rand_arr, 1, 500000)
elapsed time: 0.000699786 seconds (160 bytes allocated)
julia>@time @inbounds ret_2= unroll_sum(rand_arr,1,500000)
elapsed time: 0.000383906 seconds (96 bytes allocated)

调用C或Fortran加速

尽管Julia声称速度和C相提并论,但是并不是所有的情况下都能有C的性能。合理地使用像LAPACK,BLAS,MUMPS这类已经高度优化的函数库有助于提升运行速度。

尽量使用一维数组

多维数组相当于多重指针,读取需要多次读地址,用一维数组能节约读取时间, 但注意一维数组的排列, Julia和MATLAB以及Fortran一样都是列优先储存。对于可以并行的一维数组操作,Julia提供了宏 @simd (0.3.0,可能会在以后版本中取消)。

良好的编程习惯

这是对Julia官方手册的一句话总结,包括声明变量类型,保持函数的健壮性,循环前预分配空间等等。