【问题标题】:Trying to improve execution time of Julia code试图改善 Julia 代码的执行时间
【发布时间】:2017-06-09 17:30:21
【问题描述】:

我需要编写一个代码来使用只有一个规则的语法生成一个字符串。例如,如果规则是“G -> [G+G]”,我们将规则应用到“G”,结果是字符串“[G+G]”;如果我们将它应用到之前的结果,我们会得到“[[G+G]+[G+G]]”等等。换句话说,它是关于重写公理(规则的左侧)给定的次数, 遵循规则。

我收到了一段用 Octave 编写的代码,用于实现此操作(我不会包含代码,因为它有点长,但如果有必要理解或回答问题,我会包含)。我需要做的是在 Julia 中编写一个等效的函数;所以我写了这个

    function generate_developedstring(axiom::ASCIIString, genome::ASCIIString, iterations::Int8)
        tic()
        developedstring = axiom
        for i=1:iterations
            developedstring = replace(developedstring, axiom, genome)
        end
        toc()
        return developedstring
    end

在我之前写的例子中,公理是“G”,基因组是“[G+G]”。

根据 julialang.org 发布的基准时间,Julia 应该比 Octave 快得多,但在这种情况下,Octave 是 Julia 的两倍 (我对两个代码都使用了相同的公理、基因组和迭代,并使用 tic toc 函数测量了时间)。

有什么方法可以让 Julia 代码更快?

编辑:首先,非常感谢你们的cmets。我会向你展示我得到的 Octave 代码(不是我写的):

function axiom = ls(genome)
  tic
  ProductionSystem = ['[=>[ ]=>] +=>+ -=>- G=>',genome];
  rule = extract(ProductionSystem);

  n_Rules = length(rule);

  % starting string
  axiom = 'G';

  % iterations (choose only from 1 to 7, >= 8 critical,
  % depends on the string and on the computer !!
  n_Repeats = 3;

%CALCULATE THE STRING
%=================================

for i = 1:n_Repeats
    % a single letter (axiom)
    axiomINcells = cellstr(axiom);
    for j = 1:n_Rules
        % find all occurrences of that axiom
        hit = strfind(axiom, rule(j).pre);
        if (length(hit) >= 1)
            for k = hit
                % perform the rule
                % (replace 'pre' by 'post')
                axiomINcells{k} = rule(j).pos;
            end
        end
    end
    axiom = [];
    for j = 1:length(axiomINcells)
        % put all strings together
        axiom = [axiom, axiomINcells{j}];
    end
end
toc

function rule = extract(ProductionSystem)
% rules are separated by space character, and pre and post sides are
% separtated by '->'
% e.g. F->FF G->F[+G][-G]F[+G][-G]FG

i=0;
while (~isempty(ProductionSystem))
    i=i+1;
    [rule1,ProductionSystem] = strtok(ProductionSystem,' ');
    [rule(i).pre,post] = strtok(rule1,'=>');
    rule(i).pos = post(3:end);
    if (~isempty(ProductionSystem)) ProductionSystem=ProductionSystem(2:end);   % delete separator
    end
end

关于我使用的 Julia 版本,它是 0.4.7。你还问我需要它跑多快;我只需要尽可能快地编写代码,而 Octave 更快的事实让我觉得我做错了什么。 再次感谢。

【问题讨论】:

  • 一些建议首先:在函数内部使用@time宏而不是tic/toc/,并进行两次基准测试,以排除编译时间。如果您在讨论论坛discourse.julialang.org 上发布一个最小的、可运行的示例,您可能会在此类性能问题上获得更多有用的帮助
  • Octave 代码是否都是一个矢量化调用?如果是,那么您就不是在比较 Octave 本身。相反,您会将 Julia 与可能在 C 中实现的优化良好的函数进行比较。您不应该期望 Julia 比 C 做得更好,尽管它通常应该很接近。
  • 我在这上面花了一些时间,Julia 版本似乎很快——迭代字符串版本与直接递归打印单个字符串一样快,并且预分配适量的前面的空间。您能否发布 Octave 代码,以便我们查看它在做什么?
  • 假设您的 replace 函数每次都制作一个新副本,这可以替换为该函数的 mutating 版本,(名为 replace!)而是现有的对象,避免内存分配。为此,developedstring 可以有一些填充,以确保它不必在每次迭代时更改大小。
  • replace! 不存在?因为Strings 是不可变的。

标签: octave julia grammar


【解决方案1】:

您能否将genome 指定为规则而不是模式?我的意思是,例如genome = ax -> "[$ax,$ax]"

比较这两个实现,第一个和你的一样:

function genstring(axiom::String, genome::String, iter::Int)
    str = axiom
    for i in 1:iter
        str = replace(str, axiom, genome)
    end
    return str
end

然后使用匿名函数:

genome = ax -> "[$ax,$ax]"

function genstring_(axiom::String, genome, n::Int)
    if n < 1
        return axiom
    end
    return genstring_(genome(axiom), genome, n-1)
end

在 0.5 版上,BenchmarkTools

julia> @benchmark genstring("G", "[G,G]", 2)
BenchmarkTools.Trial: 
  memory estimate:  752.00 bytes
  allocs estimate:  15
  --------------
  minimum time:     745.950 ns (0.00% GC)
  median time:      801.067 ns (0.00% GC)
  mean time:        1.006 μs (14.30% GC)
  maximum time:     50.271 μs (96.63% GC)
  --------------
  samples:          10000
  evals/sample:     119
  time tolerance:   5.00%
  memory tolerance: 1.00%

julia> @benchmark genstring_("G", genome, 2)
BenchmarkTools.Trial: 
  memory estimate:  352.00 bytes
  allocs estimate:  9
  --------------
  minimum time:     397.562 ns (0.00% GC)
  median time:      414.149 ns (0.00% GC)
  mean time:        496.511 ns (13.06% GC)
  maximum time:     24.410 μs (97.18% GC)
  --------------
  samples:          10000
  evals/sample:     201
  time tolerance:   5.00%
  memory tolerance: 1.00%

它的扩展性更好:

julia> @benchmark genstring("G", "[G,G]", 10)
BenchmarkTools.Trial: 
  memory estimate:  18.00 kb
  allocs estimate:  71
  --------------
  minimum time:     93.569 μs (0.00% GC)
  median time:      95.959 μs (0.00% GC)
  mean time:        103.429 μs (3.05% GC)
  maximum time:     4.216 ms (97.14% GC)
  --------------
  samples:          10000
  evals/sample:     1
  time tolerance:   5.00%
  memory tolerance: 1.00%

julia> @benchmark genstring_("G", genome, 10)
BenchmarkTools.Trial: 
  memory estimate:  14.13 kb
  allocs estimate:  49
  --------------
  minimum time:     3.072 μs (0.00% GC)
  median time:      3.597 μs (0.00% GC)
  mean time:        5.703 μs (29.78% GC)
  maximum time:     441.515 μs (98.24% GC)
  --------------
  samples:          10000
  evals/sample:     8
  time tolerance:   5.00%
  memory tolerance: 1.00%

据我所知,字符串插值并不是超快的,因此可以进一步优化。

【讨论】:

  • 匿名函数在 Julia v0.6 中几乎没有性能损失是真的吗?如果是这样,那么您的第二个解决方案将在 Julia 的开发副本上更快。
  • 性能改进已经在 v0.5 中发生。不过,将会有一个新的字符串实现。不确定这会对性能产生什么影响。
  • 非常感谢,使用匿名函数的程序速度快得多。
猜你喜欢
  • 2021-09-26
  • 2015-05-27
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
  • 1970-01-01
  • 1970-01-01
  • 2010-12-16
  • 2020-08-15
相关资源
最近更新 更多