Lua挑战:你能改进mandelbrot实现的性能吗?

时间:2009-02-20 16:42:37

标签: optimization lua mandelbrot

状态:到目前为止,最佳答案的程序在原始程序的33%的时间内执行!但可能还有其他方法可以优化它。


Lua目前是最快的脚本语言,但Lua在针对C / C ++的几个基准测试中得分非常糟糕。

其中一个是mandelbrot测试(Generate Mandelbrot设置便携式位图文件N = 16,000),其中得分可怕1:109(多核)或1:28(单核)

由于Delta的速度非常大,因此这是优化的理想选择。此外,我确信那些知道Mike Pall是谁的人可能认为不可能进一步优化这一点,但这显然是错误的。任何做过优化的人都知道总是可以做得更好。此外,我通过一些调整设法获得了一些额外的性能,所以我知道它可能:)

-- The Computer Language Shootout
-- http://shootout.alioth.debian.org/
-- contributed by Mike Pall

local width = tonumber(arg and arg[1]) or 100
local height, wscale = width, 2/width
local m, limit2 = 50, 4.0
local write, char = io.write, string.char

write("P4\n", width, " ", height, "\n")

for y=0,height-1 do
  local Ci = 2*y / height - 1
  for xb=0,width-1,8 do
    local bits = 0
    local xbb = xb+7
    for x=xb,xbb < width and xbb or width-1 do
      bits = bits + bits
      local Zr, Zi, Zrq, Ziq = 0.0, 0.0, 0.0, 0.0
      local Cr = x * wscale - 1.5
      for i=1,m do
        local Zri = Zr*Zi
        Zr = Zrq - Ziq + Cr
        Zi = Zri + Zri + Ci
        Zrq = Zr*Zr
        Ziq = Zi*Zi
        if Zrq + Ziq > limit2 then
          bits = bits + 1
          break
        end
      end
    end
    if xbb >= width then
      for x=width,xbb do bits = bits + bits + 1 end
    end
    write(char(255-bits))
  end
end

那么如何对其进行优化(当然,与任何优化一样,您必须测量实施以确保更快)。并且你不允许为此改变Lua的C核心,或者使用LuaJit,它可以找到优化Lua弱点之一的方法。

修改:在此基础上获得奖励,使挑战更有趣。

9 个答案:

答案 0 :(得分:7)

通过2,比我以前更好(在我的机器上)约30%。主要的节省来自展开内部循环以分摊开销。

还包括(已注释掉)是当你陷入中心心形时,通过提前退出(并将像素设置为黑色)来节省时间的中止尝试。它有效,但无论我如何摇晃它都会慢一些。

我必须跑步,但我会留下一个离别的建议。可能通过对结果进行行程编码来进行一些优化(因此,不是保存一堆bit-twiddled字符,而是保存列表(白点数,黑点数,白点数等)。 )。这会:

  1. 降低存储/ GC开销
  2. 允许对输出生成进行一些优化(当数字为&gt;&gt; 8时)
  3. 允许一些轨道探测。
  4. 不知道它是否可以被编码得足够紧密以便飞行,但如果我有更多时间的话,我会尝试下一步。

    -- The Computer Language Shootout
    -- http://shootout.alioth.debian.org/
    -- contributed by Mike Pall
    -- with optimizations by Markus J. Q. (MarkusQ) Roberts
    
    local width = tonumber(arg and arg[1]) or 100
    local height, wscale = width, 2/width
    local m, limit2 = 50, 4.0
    local write, char = io.write, string.char
    
    local h2 = math.floor(height/2)
    local hm = height - h2*2
    local top_half = {}
    
    for y=0,h2+hm do
        local Ci = 2*y / height - 1
        local line = {""}
        for xb=0,width-1,8 do
            local bits = 0
            local xbb = xb+7
            for x=xb,xbb < width and xbb or width-1 do
                bits = bits + bits
                local Zr, Zi, Zrq, Ziq = 0.0, 0.0, 0.0, 0.0
                local Cr = x * wscale - 1.5
                local Zri = Zr*Zi
                for i=1,m/5 do
                    Zr = Zrq - Ziq + Cr
                    Zi = Zri + Zri + Ci
                    Zri = Zr*Zi
    
                    Zr = Zr*Zr - Zi*Zi + Cr
                    Zi = 2*Zri +         Ci
                    Zri = Zr*Zi
    
                    Zr = Zr*Zr - Zi*Zi + Cr
                    Zi = 2*Zri +         Ci
                    Zri = Zr*Zi
    
                    Zr = Zr*Zr - Zi*Zi + Cr
                    Zi = 2*Zri +         Ci
                    Zri = Zr*Zi
    
                    Zr = Zr*Zr - Zi*Zi + Cr
                    Zi = 2*Zri +         Ci
                    Zri = Zr*Zi
    
                    Zrq = Zr*Zr
                    Ziq = Zi*Zi
                    Zri = Zr*Zi
                    if Zrq + Ziq > limit2 then
                        bits = bits + 1
                        break
                        end
                    -- if i == 1 then
                    --    local ar,ai       = 1-4*Zr,-4*Zi
                    --    local a_r         = math.sqrt(ar*ar+ai*ai)
                    --    local k           = math.sqrt(2)/2
                    --    local br,bi2      = math.sqrt(a_r+ar)*k,(a_r-ar)/2
                    --    if (br+1)*(br+1) + bi2 < 1 then
                    --        break
                    --        end
                    --    end
                    end
                end
            for x=width,xbb do 
                bits = bits + bits + 1 
                end
            table.insert(line,char(255-bits))
            end
        line = table.concat(line) 
        table.insert(top_half,line)
        end
    
    write("P4\n", width, " ", height, "\n")
    for y=1,h2+hm do
        write(top_half[y])
        end
    for y=h2,1,-1 do
        write(top_half[y])
       end
    

答案 1 :(得分:3)

所以这里约为40%:

-- The Computer Language Shootout
-- http://shootout.alioth.debian.org/
-- contributed by Mike Pall

local width = tonumber(arg and arg[1]) or 100
local height, wscale = width, 2/width
local m, limit2 = 50, 4.0
local write, char = io.write, string.char

function addChar (line, c)
    table.insert(line, c)
    for i=table.getn(line)-1, 1, -1 do
        if string.len(line[i]) > string.len(line[i+1]) then
            break
            end
        line[i] = line[i] .. table.remove(line)
        end
    end

local h2 = math.floor(height/2)
local hm = height - h2*2
local top_half = {}
for y=0,h2+hm do
    local Ci = 2*y / height - 1
    local line = {""}
    for xb=0,width-1,8 do
        local bits = 0
        local xbb = xb+7
        for x=xb,xbb < width and xbb or width-1 do
            bits = bits + bits
            local Zr, Zi, Zrq, Ziq = 0.0, 0.0, 0.0, 0.0
            local Cr = x * wscale - 1.5
            for i=1,m do
                local Zri = Zr*Zi
                Zr = Zrq - Ziq + Cr
                Zi = Zri + Zri + Ci
                Zrq = Zr*Zr
                Ziq = Zi*Zi
                if Zrq + Ziq > limit2 then
                    bits = bits + 1
                    break
                    end
                end
            end
        for x=width,xbb do 
            bits = bits + bits + 1 
            end
        addChar(line,char(255-bits))
        end
    line = table.concat(line) 
    table.insert(top_half,line)
    end

write("P4\n", width, " ", height, "\n")
for y=1,h2+hm do
    write(top_half[y])
    end
for y=h2,1,-1 do
    write(top_half[y])
    end

- MarkusQ

答案 2 :(得分:2)

现在至少有一个答案比我的解决方案快,我会发布我的答案

-- The Computer Language Shootout
-- http://shootout.alioth.debian.org/
-- contributed by Mike Pall

local width = tonumber(arg and arg[1]) or 100
local height, wscale = width, 2/width
local m, limit2 = 50, 4.0
local write, char = io.write, string.char
local insert = table.insert
local results={}
write("P4\n", width, " ", height, "\n")

for y=0,height-1 do
  local Ci = 2*y / height - 1
  for xb=0,width-1,8 do
    local bits = 0
    local xbb = xb+7
    for x=xb,xbb < width and xbb or width-1 do
      bits = bits + bits
      local Zr, Zi, Zrq, Ziq = 0.0, 0.0, 0.0, 0.0
      local Cr = x * wscale - 1.5
      for i=1,m do
        local Zri = Zr*Zi
        Zr = Zrq - Ziq + Cr
        Zi = Zri + Zri + Ci
        Zrq = Zr*Zr
        Ziq = Zi*Zi
        if Zrq + Ziq > limit2 then
          bits = bits + 1
          break
        end
      end
    end
    if xbb >= width then
      for x=width,xbb do bits = bits + bits + 1 end
    end
    insert(results,(char(255-bits)))
  end
end
write(table.concat(results))

技巧是在将值发送到输出之前将值存储到表中。 像这样简单的东西将运行时间减少到58%。

答案 3 :(得分:2)

我不知道Lua生产工作代码是好事,但你应该能够通过使用一些数学技巧来大幅增加Mandelbrot的性能。有人建议使用对称来加速它,使用这种优化可以做另一项重大改进:

使用使用Mandelbrot部分的矩形坐标的递归函数。然后计算矩形边界线处的值和中间分割的两条线。在此之后,有4个子矩形。如果其中一个具有相同的边框像素颜色,则可以使用此颜色填充它,如果没有,则递归调用此部分的函数。

我搜索了这个算法的另一个解释,发现了一个here - 以及一个不错的visualization。旧的DOS程序FRACTINT调用此optimization“Tesseral模式”。

答案 4 :(得分:1)

答案 5 :(得分:1)

为什么要使用局部变量Zri?可以通过重新排序下两个语句来避免使用它:

Zi = 2 * Zr * Zi + Ci Zr = Zrq-Ziq + Cr

也可以使用简单的周期性检查,但加速取决于m。越大的“m”越大,从周期性检查中获得的加速就越好。

答案 6 :(得分:0)

罗伯特古尔德&gt; 其中一个是mandelbrot测试(Generate Mandelbrot设置便携式位图文件N = 16,000),在那里得分可怕的1:109

当你引用基准游戏中的数字时,请显示这些数字的来源,以便读者有一些背景。

在这种情况下,您似乎已经在四核计算机上测量了数字,其中最快的程序已被重写以利用多个核心。而不是查看经过的时间sort by CPU time and you'll see the ratio drop to 1:28

或者查看中位数和四分位数以获得对how the set of C++ measurements compares to the set of Lua measurements的更好印象。

或者有一整套测量,程序被迫只使用一个核心 - Lua compared with C++ - 如果你看一下those Lua pi-digits programs,你会发现他们使用的是C语言GNU GMP库。

答案 7 :(得分:0)

我做的下一步是缓存一遍又一遍计算的东西,并将bit + bit替换为bit * 2,这些简单的优化非常强大......

local width = tonumber(arg and arg[1]) or 100
local height, wscale = width, 2/width
local m, limit2 = 50, 4.0
local write, char = io.write, string.char
local results={}
write("P4\n", width, " ", height, "\n")
local height_minus_one = height - 1
local width_minus_one = width -1

for y=0,height_minus_one do
  local Ci = 2*y / height_minus_one
  for xb=0,width_minus_one,8 do
    local bits = 0
    local xbb = xb+7
    for x=xb,xbb < width and xbb or width_minus_one do
      bits = bits *2
      local Zr, Zi, Zrq, Ziq = 0.0, 0.0, 0.0, 0.0
      local Cr = x * wscale - 1.5
      for i=1,m do
        local Zri = Zr*Zi
        Zr = Zrq - Ziq + Cr
        Zi = Zri + Zri + Ci
        Zrq = Zr*Zr
        Ziq = Zi*Zi
        if Zrq + Ziq > limit2 then
          bits = bits + 1
          break
        end
      end
    end
    if xbb >= width then
      for x=width,xbb do bits = bits *2 + 1 end
    end
    table.insert(results,(char(255-bits)))
  end
end
write(table.concat(results))

这种优化使得程序在原始时间的34%的时间内运行,但Markus Q的优化仍然优于我的;)

答案 8 :(得分:0)

这是另一次尝试,但事实证明它比变量的本地访问慢(我想象使用干净的环境会使查找变量更快,但事实并非如此,本地的虚拟寄存器稍微有点更快)这使运行时间降至41%。

local env={}
env.width = tonumber(arg and arg[1]) or 100
env.height = env.width
env.wscale = 2/env.width
env.m = 50
env.limit2 = 4.0
env.write = io.write
env.char = string.char
env.results={}
env.height_minus_one = env.height - 1
env.width_minus_one = env.width -1
env.insert = table.insert

setfenv(function()
    write("P4\n", env.width, " ", env.height, "\n")
    for y=0,height_minus_one do
      local Ci = 2*y / height_minus_one
      for xb=0,width_minus_one,8 do
        local bits = 0
        local xbb = xb+7
        for x=xb,xbb < width and xbb or width_minus_one do
          bits = bits *2
          local Zr, Zi, Zrq, Ziq = 0.0, 0.0, 0.0, 0.0
          local Cr = x * wscale - 1.5
          for i=1,m do
            local Zri = Zr*Zi
            Zr = Zrq - Ziq + Cr
            Zi = Zri + Zri + Ci
            Zrq = Zr*Zr
            Ziq = Zi*Zi
            if Zrq + Ziq > limit2 then
              bits = bits + 1
              break
            end
          end
        end
        if xbb >= width then
          for x=width,xbb do bits = bits *2 + 1 end
        end
        insert(results,(char(255-bits)))
      end
    end
end,env)()

io.write(table.concat(env.results))