当前位置:K88软件开发文章中心编程语言非主流编程语言Julia → 文章内容

Julia 代码性能优化

减小字体 增大字体 作者:佚名  来源:网上搜集  发布时间:2019-1-15 16:28:31

由 陈 创建, 最后一次修改 2016-08-12 代码性能优化以下几节将描述一些提高 Julia 代码运行速度的技巧。避免全局变量全局变量的值、类型,都可能变化。这使得编译器很难优化使用全局变量的代码。应尽量使用局部变量,或者把变量当做参数传递给函数。对性能至关重要的代码,应放入函数中。声明全局变量为常量可以显著提高性能:const DEFAULT_VAL = 0使用非常量的全局变量时,最好在使用时指明其类型,这样也能帮助编译器优化: global xy = f(x::Int + 1)写函数是一种更好的风格,这会产生更多可重复和清晰的代码,也包括清晰的输入和输出。使用 @time 来衡量性能并且留心内存分配衡量计算性能最有用的工具是 @time 宏。下面的例子展示了良好的使用方式 : julia> function f(n) s = 0 for i = 1:n s += i/2 end s end f (generic function with 1 method) julia> @time f(1) elapsed time: 0.008217942 seconds (93784 bytes allocated) 0.5 julia> @time f(10^6) elapsed time: 0.063418472 seconds (32002136 bytes allocated) 2.5000025e11在第一次调用时 (@time f(1)), f 会被编译. (如果你在这次会话中还没有使用过 @time, 计时函数也会被编译.) 这时的结果没有那么重要. 在第二次调用时, 函数打印了执行所耗费的时间, 同时请注意, 在这次执行过程中分配了一大块的内存. 相对于函数形式的 tic 和 toc, 这是@time 宏的一大优势.出乎意料的大块内存分配往往意味着程序的某个部分存在问题, 通常是关于类型稳定性. 因此, 除了关注内存分配本身的问题, 很可能 Julia 为你的函数生成的代码存在很大的性能问题. 这时候要认真对待这些问题并遵循下面的一些个建议.另外, 作为一个引子, 上面的问题可以优化为无内存分配 (除了向 REPL 返回结果), 计算速度提升 30 倍 :: julia> @time f_improved(10^6) elapsed time: 0.00253829 seconds (112 bytes allocated) 2.5000025e11你可以从下面的章节学到如何识别 f 存在的问题并解决.在有些情况下, 你的函数可能需要为本身的操作分配内存, 这样会使得问题变得复杂. 在这种情况下, 可以考虑使用下面的 :ref:`工具` 之一来甄别问题, 或者将函数拆分, 一部分处理内存分配, 另一部分处理算法 (参见 :ref:`预分配内存 `).## 工具Julia 提供了一些工具包来鉴别性能问题所在 :- [profiling](http://julia-cn.readthedocs.org/zh_CN/latest/stdlib/profile/#stdlib-profiling) 可以用来衡量代码的性能, 同时鉴别出瓶颈所在. 对于复杂的项目, 可以使用 `ProfileView ` 扩展包来直观的展示分析 结果.- 出乎意料的大块内存分配, -- ``@time``, ``@allocated``, 或者 -profiler - 意味着你的代码可能存在问题. 如果你看不出内存分配的问题, -那么类型系统可能存在问题. 也可以使用 ``--track-allocation=user`` 来 -启动 Julia, 然后查看 ``*.mem`` 文件来找出内存分配是在哪里出现的.- `TypeCheck `_ 扩展包可以指出程序一 些问题.## 避免包含一些抽象类型参数当运行参数化类型时候,比如 arrays,如果有可能最好去避免使用抽象类型参数。思考下面的代码:```a = Real[] # typeof(a) = Array{Real,1}if (f = rand()) x = [1 2; 3 4] 2x2 Array{Int64,2}: 1 2 3 4julia> x[:]4-element Array{Int64,1}: 1 3 2 4```这种给数组排序的约定在许多语言中都是常见的,比如 Fortran , Matlab ,和 R 语言(举几个例子来说)。以列为主序的另一选择就是以行为主序,其它语言中的 C 语言和 Python 语言(``numpy``)就是选用了这种方式。记住数组的顺序对数组的查找有着至关重要的影响。要记住的一个查找规则就是对于基于列为顺序的数组,第一个指针是变化最快的。这基本上就意味着如果在一段代码中,循环指针是第一个,那么查找速度会更快。我们来看一下下面这个人为的例子。假设我们想要实现一个功能,接收一个 ``Vector`` 并且返回一个方形的 ``Matrix``,且行或列为输入矢量的复制。我们假设是行还是列为数据的复制并不重要(或许剩下的代码可以相应地更容易的适应)。我们可以想到有至少四种方法可以实现这一点(除了建议的回访正建的 ``repmat`` 功能): ```function copy_cols{T}(x::Vector{T}) n = size(x, 1) out = Array(eltype(x), n, n) for i=1:n out[:, i] = x end outendfunction copy_rows{T}(x::Vector{T}) n = size(x, 1) out = Array(eltype(x), n, n) for i=1:n out[i, :] = x end outendfunction copy_col_row{T}(x::Vector{T}) n = size(x, 1) out = Array(T, n, n) for col=1:n, row=1:n out[row, col] = x[row] end outendfunction copy_row_col{T}(x::Vector{T}) n = size(x, 1) out = Array(T, n, n) for row=1:n, col=1:n out[row, col] = x[col] end outend```现在我们使用同样的输入向量 ``1`` 产生的随机数 ``10000`` 给每个功能计时:```julia> x = randn(10000);julia> fmt(f) = println(rpad(string(f)*": ", 14, ' '), @elapsed f(x))julia> map(fmt, {copy_cols, copy_rows, copy_col_row, copy_row_col});copy_cols: 0.331706323copy_rows: 1.799009911copy_col_row: 0.415630047copy_row_col: 1.721531501```注意到 ``copy_cols`` 比 ``copy_rows`` 快很多。这是意料之中的,因为 ``copy_cols`` 遵守 ``Matrix`` 界面的基于列的存储,并且一次就填满一列。除此之外,``copy_col_row`` 比 ``copy_row_col`` 快很多,因为它符合我们的查找规则,即在一段代码中第一个出现的元素应该是与最内部的循环相联系的。 ## 输出预先分配如果你的功能返回了一个 Array 或其它复杂类型,它可能不得不分配内存。不幸的是,时常分配和它的相反事件,垃圾区收集,是有实质性瓶颈的。 有时候,你可以在访问每个功能时通过预先分配输出来避开分配内存的需要。作为一个很小的例子,比较一下 ```function xinc(x) return [x, x+1, x+2]endfunction loopinc() y = 0 for i = 1:10^7 ret = xinc(i) y

[1] [2]  下一页


Julia 代码性能优化