性能优化常见问题


#1

我想在这里总结一下优化性能的时候一些常见的问题。如果有写得不当之处请大家指正。

为了优化而使用非 Int 整数如 UInt 或者 UInt8

这个做法其实并不明智。大多数人可能想过使用非 Int 类型来做到数组里的indexing,如 A[0x1] 。但是这其实并不会有任何对速度的优化,而且可能会有害处。

using BenchmarkTools

dest = zeros(250); src = zeros(250);

@inline function foo!(dest, src) # 假设dest长度很小
    len::UInt8 = UInt8(length(dest)) # 保证len在整个函数都是UInt8
    @boundscheck @assert len == length(src)
    @inbounds for i in 0x1:len
        dest[i] = abs(src[i])
    end
    dest
end

@inline function goo!(dest, src)
    len = length(dest)
    @boundscheck @assert len == length(src)
    @inbounds for i in 1:len
        dest[i] = abs(src[i])
    end
    dest
end

julia> @btime @inbounds goo!($dest, $src); #用Int
  24.425 ns (0 allocations: 0 bytes)

julia> @btime @inbounds foo!($dest, $src); #用UInt8
  24.796 ns (0 allocations: 0 bytes)

julia> VERSION
v"1.0.0"

可以看出,用 UInt8 没有优势,但是限制了程序( destsrc 不能太长)。

注: @inbounds 是去除bounds check。如:

julia> koo(x) = x[1000]; koo2(x) = @inbounds x[1000];

julia> koo(src)
ERROR: BoundsError: attempt to access 250-element Array{Float64,1} at index [1000]
Stacktrace:
 [1] getindex at ./array.jl:731 [inlined]
 [2] koo(::Array{Float64,1}) at ./REPL[91]:1
 [3] top-level scope at none:0

julia> koo2(src)
0.0

@boundscheck 告诉Julia,这个宏后面的东西在有 @inbounds 时删掉,但要注意的是有 @boundscheck 的函数前面要写 @inline 让Julia把这个函数内联。


关于“性能优化”分类
#2

看到 for 循环就想加 @simd

可能很多人听过 @simd 会加快速度,所以就看到 for 前面就来个 @simd。但是这个做法是不一定好。

help?> @simd
  @simd

  Annotate a for loop to allow the compiler to take extra liberties to allow loop re-ordering

文档上说,@simd 允许编译器对循环重新排序。所以说,在循环不能重新排序(下一个迭代关于上一个迭代)的时候,不要用 @simd。例如:

using BenchmarkTools

src = zeros(1000);
function foo!(v)
    @inbounds v[end] = 1
    @inbounds for i in length(v)-1:-1:1
        v[i+1] = v[i]
    end
    v
end
function foosimd!(v)
    @inbounds v[end] = 1
    @inbounds @simd for i in length(v)-1:-1:1
        v[i+1] = v[i]
    end
    v
end

julia> @btime foo!($src);
  300.412 ns (0 allocations: 0 bytes)

julia> @btime foosimd!($src);
  571.734 ns (0 allocations: 0 bytes)

julia> VERSION
v"1.0.0"

这里

for i in length(v)-1:-1:1
    v[i+1] = v[i]
end

的下一个迭代会用上一个迭代的结果,所以不要用 @simd。没有 @simd300.412 ns@simd571.734 ns


加权Jacobi的迭代优化
#3

使用uint是因为size本身是一种数据类型,既然julia没有负数index,那么符号位就等于是浪费,白白减少最大可索引数。julia是general purpose的,但是没有usize在内存紧张的场景直接崩盘。


#4

为了优化而使用非 Int 整数如 UInt 或者 UInt8

但是这其实并不会有任何对速度的优化,而且可能会有害处。

这里是说为了速度优化而用usize。


#5