编写高效Lua代码的方法 - 4 - 减少,重用,回收

LUA学习路线汇总 语法   Lua程序设计Programming in Lua   果冻想-Lua 语法入门 源码   the implementation of lua 50 英文原版 中文翻译   Lua源码鉴赏-云风   探索Lua52内部实现   详解关于Lua源码分析学习教程   Lua源码 应用   果冻想其他系列    Lua51 参考手册    Lua53 参考手册   云风博客   Lua Pr 阅读详情

翻译自《Lua Programming Gems》Chapter 2:Lua Performance Tips:Basic fact By Roberto Ierusalimschy

编写高效Lua代码的方法

减少,重用,回收(Reduce, Reuse, Recycle)

当处理Lua资源时,我们应当遵守跟利用地球资源一样的3R's原则。

    减少是最简单的一种途径。有几种方法可以避免创建对象。例如,如果你的程序使用了大量的表,你可以考虑改变它的数据表示方式。举个简单的例子,假如你的程序需要处理多边形。在Lua里表示一个多边形最自然的方式就是表示成一个点的列表,像这样:

polyline = { { x = 10.3, y = 98.5 },
                 { x = 10.3, y = 18.3 },
                 { x = 15.0, y = 98.5 },
                 ...
                }

尽管这是很自然的一种方式,但是当多边形很大的时候,由于每个点都要用一个表来存储,这种方式就显得有点不太经济了。第一种改进方法是该用数组来存储,这会内存使用量减少:

polyline = { { 10.3, 98.5 },
                 { 10.3, 18.3 },
                 { 15.0, 98.5 },
                 ...
                }

一个有一百万个点的多边形,这种改变会将内存使用从95KB降到65KB。当然,你牺牲了程序的可读性作为代价:p[i].x要比p[i][1]让人容易看得懂。

    另一个更经济的方法是用一个列表来存储x轴的值,另一个列表存储y轴的值:

polyline = { x = { 10.3, 10.3, 15.0, ...},
                  y = { 98.5, 18.3, 98.5, ...}
                }

之前的p[i].x就是现在的p.x[i]。使用这种方式,一个有一百万个点的多边形使用的内存只有24KB。

    循环体内是找到降低不必要资源创建的地方。例如,如果在一个循环中创建了一个不会改变内容的表,你可以把表放在循环体之外,或者甚至放在执行这段代码的函数之外。比较:

function foo (...)
    for i =1, n do
       local t = {1, 2, 3, "hi"}
        -- 做一些不改变t的操作。
        ...
    end
end

local t = {1, 2, 3, "hi"} -- 一劳永逸
function foo (...)
    for i =1, n do
        -- 做一些不改变t的操作。
        ...
    end
end

同样的技巧还可以用到闭包中,只要不要将其移至闭包需要的变量的作用域之外。例如,考虑一下函数:

function changenumbers (limit, delta)
    for linein io.lines() do
        line= string.gsub(line, "%d+", function (num)
            num =tonumber(num)
            ifnum >= limit then return tostring(num + delta) end
            --else return nothing, keeping the original number
        end)
       io.write(line, "\n")
    end
end

我们可以通过将内嵌的函数放在循环体之外来避免读取每行数据都要创建一个闭包:

function changenumbers (limit, delta)
    localfunction aux (num)
        num =tonumber(num)
        if num>= limit then return tostring(num + delta) end
        end
    for linein io.lines() do
        line =string.gsub(line, "%d+", aux)
       io.write(line, "\n")
    end
end

然而,我们不能将函数aux搬到函数changenumbers之外,因为在那里函数aux不能访问变量limit和delta。

    对于大多数字符串操作,我们都可以通过下标在已存在的字符串上工作,从而减少字符串的创建。例如,函数string.find返回字符串上匹配正则表达式的下标,而不是返回一个匹配的字符串。返回下标,就避免了在成功匹配时创建一个新的(子)字符串。开发者需要时,可以通过函数string.sub来获取匹配的子字符串。 

    当我们不能避免使用新的对象的时候,我们还是可以通过重用来避免创建这些对象。考虑字符串的重用是没有必要的,因为Lua已经替我们做了这些工作:Lua总是将用到的字符串内部化,不会放过任何重用的机会。重用表则是比较有效。举一个常见的例子,让我们回到在一个循环体内部创建表的情况。不同的是这次的表的内容不是固定不变的。不过,我们往往还是可以简单地更改表的内容从而能够在所有迭代中重用这个表。考虑一下代码:

local t = {}
for i = 1970, 2000 do
    t[i] =os.time({year = i, month = 6, day = 14})
end
一下是与之等价的代码,但是重用了表:
local t = {}
local aux = {year = nil, month = 6, day = 14}
for i = 1970, 2000 do
    aux.year =i
    t[i] =os.time(aux)
end

另一种比较有用的实现重用的方法是记忆化(memoizing)。原理很简单:对于一个给定的某些输入值,保存其计算结果,当同样的值输入时,程序只需重用之前保存的结果。

    LPeg,Lua中一个新的模式匹配的包,有趣地使用了记忆化方法。LPeg把每个模式都编译成一种内部的格式,负责匹配的分析器把这些格式的编码看成一个个“程序”。这个编译动作相对于匹配动作来说是比较耗费资源的。所以,LPeg把编译的结果记忆化,从而达到重用的目的。它通过在一个表里面把模式字符串以及其对应的内部格式关联起来来实现。

    记忆化方法的一个比较普遍的问题是,保存之前计算结果所耗费的空间可能会掩盖重用这些结果的好处。为了在Lua中解决这个问题,我们可以使用一个弱表来保存计算结果,这样,不用的结果就会从表中被删除。

    在Lua里,有了更高等的函数(译注,Lua的函数是一等类型,即Lua处理函数和变量的方式是一样的),我们可以定义一个通用的记忆化函数:


function memoize (f)
    local mem= {} -- memoizing table
   setmetatable(mem, {__mode = "kv"}) -- make it weak
    returnfunction (x) -- new version of ’f’, with memoizing
        local r = mem[x]
        if r== nil then -- no previous result?
           r = f(x) -- calls original function
           mem[x] = r -- store result for reuse
        end
       return r
    end
end
对于一个给定的函数f,memoize(f)返回一个新的函数,这个函数会返回跟f一样的结果,但是会吧结果记录下来。例如,我们可以重新定义loadstring函数的一个记忆化版本:
loadstring = memoize(loadstring)

我们像使用老 函数一样使用这个新函数,但是如果我们加载很多重复的字符串的时候,我们将会从性能上获得很大的收益。

    如果你的程序创建和释放过多的协程的时候,回收是一个提高程序性能的又一选择。目前协程的API并没有直接提供重用一个协程的方法,但是我们可以规避这个限制。考虑以下协程:

co = coroutine.create(function (f)
    while fdo
        f =coroutine.yield(f())
    end
end

这个协程接受一个作业(一个将要被执行的函数),运行这个作业,结束后等待下一个作业。

    Lua中的大多数回收都是由垃圾收集器自动完成的。Lua使用一个增量垃圾收集器。这意味着收集器每次都执行一小步动作(增量地),跟程序一起交错执行。每一步的工作量是跟程序的内存申请量成正比的:Lua申请了多少内存,垃圾收集器就做相当比例的工作。程序消耗内存越快,收集器就越快地尝试回收内存。

    如果我们在程序中遵守减少和重用的准则,通常收集器没有太多的事情做。但是有时候我们不能避免创建大量的垃圾,这时收集器的工作就变得繁重了。Lua的垃圾收集器是用来调整程序平衡的,所以再大多数程序中,它的表现都是很合理的。但是有些特殊情况,我们还是可以通过更好地调整收集器提高程序的性能的。

    在Lua里,我们可以通过调用函数collectgarbage来控制垃圾收集器,在C里则是调用lua_gc。尽管接口不同,以上两个函数基本上提供了相同的功能。接下来的讨论我会使用Lua的接口,但是这种操作往往在C里面做会更好。

    函数collectgarbage提供了几种功能:它可以停止和重启收集器,强制进行一次完成的收集,强制执行一步收集,得到Lua使用的总内存量,更改两个影响到收集步伐的参数。所有这些操作在需要大量内存的程序里都有其用武之地。

    一些批处理程序,它们创建了若干结构体,根据那些结构体产生一些输出,然后退出(比如编译器)。“永远”停止收集器将是一个好选择。对于那些程序,垃圾收集是比较浪费时间的,因为可回收的垃圾很少,并且程序一旦退出,所有的内存就会被释放了。

    对于一些非批处理的程序,永远关闭收集器就不是个好选择了。尽管如此,在程序的一些关键时间点关闭收集器还是有好处的。必要的时候,还可以由程序来完全控制垃圾收集器:收集器总是处于关闭状态,只有程序显式地要求执行一个步骤或者执行一个完整的回收时,收集器才开始工作。例如,有些事件驱动的平台会提供一个idle函数,这个函数会在没有事件可以处理时被调用。这是执行垃圾收集的最好时刻。(在Lua5.1中,每次在收集器关闭时强制执行一些收集工作都会使收集器自动启动。所以为了保持收集器做完马上关闭,你必须在强制执行一些收集操作之后马上调用collectgarbage("stop")。)

    最后一个方法,你可以尝试改变收集器的参数。收集器由两个参数控制其收集步伐。第一个是“pause”(暂停),控制收集器在一轮回收结束后隔多久才开始下一轮的回收。第二个参数是“stepmul”(即 step multiplier,步伐递增),控制收集器每一步要做多少工作。粗略地讲,暂停间隔越小,步伐递增越大,收集器工作就越快。

    这些参数对一个程序的总体性能的影响是很难预测的。很明显地,一个越快的收集器每秒耗费的CPU周期就越多;但是另一方面,这将会减少程序的总内存占用量,从而减少页面切换的几率。只有认真的实验能够让你给这些参数设定一个最好的值。

编写高效Lua代码的方法 - 总结 基础 1 尽量使用local       使用变量的效率:local > upvalue > global       local变量存放在栈中,upvalue存放在链表中,global变量存放在全局的表中。       例子:  使用local function Add() local x, y return x + y end  使用upvalue 阅读详情

相关推荐

lua垃圾回收机制

Lua 的垃圾回收(Garbage Collection, GC)机制是一种自动内存管理技术,主要基于标记-清除(Mark-and-Sweep)算法,并结合了增量式(Incremental) 和分代(Generational)优化策略(不同版本实现略有差异)。自动内存管理Lua 的 GC 自动管理内存,开发者无需手动释放对象(如等)。当对象不再被引用时,GC 会将其回收。核心算法:标记-清除标记阶段:从根节点(全局变量、活跃栈、注册表等)出发,递归标记所有可达对象为“存活”。清除阶段。

m0_45371381的博客 1201

Lua三人帮出新书 Lua Programming Gems

[url]http://www.lua.org/gems/[/url] We are proud to announce that the book has been published: Lua Programming Gems。 edited by L. H. de Figueiredo, W. Celes, R. Ierusalimschy

erlang非业余研究 570

Lua 垃圾回收

垃圾回收是一种自动管理内存的技术,它通过自动识别和回收不再使用的内存,从而避免内存泄漏和内存溢出等问题。在 Lua 中,垃圾回收器负责跟踪内存分配和释放,确保程序的稳定运行。Lua 的垃圾回收机制是一种有效的内存管理技术,它简化了内存管理,提高了开发效率。了解 Lua 垃圾回收机制,有助于程序员编写更加高效、稳定的程序。在实际开发中,我们需要根据具体情况选择合适的垃圾回收策略,并注意性能优化,以充分发挥垃圾回收的优势。

xyq2024的博客 456

编写高效Lua代码的方法

翻译自《Lua Programming Gems》Chapter 2:Lua Performance Tips:Basic fact By Roberto Ierusalimschy 基本知识 Lua在运行代码之前,会先把源码翻译(预编译)成一种内部编码,这种编码由一连串的虚拟机能够识别指令构成,与CPU的机器码很相似。接下来由C代码中的一个while循环负责解释这些内部编码,

1048

Lua Programming Gems

完整版 Lua Programming Gems

Lua Programming Gems 英文版 pdf,高清

本书是一系列文章,记录了如何在Lua中很好地编程的一些智慧和实践。

020-Lua 垃圾回收(Garbage Collection)

垃圾回收是自动化的内存管理机制,用于释放不再使用的内存资源。

小宝哥Code的专栏 1647

Lua Programming Gems 高清

Lua Programming Gems 高清文字版,7z压缩。。。。。。。。。。

Lua和JS的垃圾回收机制

对比项Lua类型增量式标记清除分代式 + 增量 + 并发 + 空闲优化可配置性高(可手动调控)低(主要由引擎内部控制)分代支持❌ 不支持✅ 支持(年轻代/老年代)调用控制等函数手动调用无公开 API,完全自动性能表现简洁但在大型项目中可能频繁 GC 卡顿高效,适用于大规模 JS 应用Lua 的 GC 更轻量、可控性强、适合嵌入式和脚本环境;JavaScript 的 GC 更复杂、高效,适合大规模 Web 应用和复杂对象图。Lua内存管理与垃圾收集机制详解-优快云博客。

北京林业大学 吴道权(C/C++) 1424

Lua Programming Gems 高清书签

Lua Programming Gems 英文高清书签 Lua Programming Gems 英文高清书签

[English_P369]Lua Programming Gems

稀有资源!!!稀有资源!!!稀有资源!!! 作者:Luiz Henrique deFigueiredo,Waldemar Cele出版社:Roberto Ierusalimschy出版时间:1988年08月

【进阶篇】2.4 Redis Lua脚本详解

Redis 其内置了脚本语言Lua,允许用户通过Lua脚本执行一系列操作。本篇博客将深入探讨Redis Lua脚本的执行机制,包括加载脚本、编译脚本和执行脚本的过程,以及脚本的原子性和事务性。我们将通过实例演示的方式,展示如何在Redis中使用Lua脚本,以及Lua脚本在Redis中的应用场景。让我们一起来了解Redis Lua脚本的强大之处吧!Lua是一种轻量级的脚本语言,它具有简洁、灵活、高效的特点。Lua语言最初是为嵌入式系统设计的,但现在已广泛应用于游戏开发、Web开发、嵌入式设备控制等领域。

日拱一卒无有尽,功不唐捐终入海 1万+

Lua5.1全三套:Lua Programming(中英文版)+中文手册

本文件包括Lua5.1的《Lua Programming》第二版的中文版、英文版以及Lua5.1的中文手册。

evolved.lua:高效灵活的Lua ECS框架

evolved.lua:高效灵活的Lua ECS框架 项目介绍 evolved.lua 是一个为 Lua 设计的快速且灵活的 ECS(Entity-Component-System)库。ECS 是一种在游戏开发和实时应用程序中常用的架构模式,它通过实体(Entities)、组件(Components)和系统(Systems)来组织数据和行为,以实现高效的数据访问和灵活的系统设计。evolved.l...

gitblog_00788的博客 927

AS5600磁编码器选型与安装避坑指南:磁铁形状、间隙距离到底怎么选?(附实测数据)

本文详细解析了AS5600磁编码器的选型与安装要点,包括磁铁形状、间隙距离等关键参数的选择,并附有实测数据支持。特别针对Arduino和I2C接口的应用场景,提供了硬件选型和机械安装的实用指南,帮助开发者避免常见陷阱,提升系统精度和稳定性。

weixin_27038245的博客 287

上机笔试题目:取出指定条件二维数组的子数组

取出指定条件二维数组的子数组: 1)传入参数为: 参数一:String[][]sArray2字符串二维数组 参数二:Stringkey字符串 参数三:StringkeyFormat含匹配符的字符串,如下:%0-%1,其中%x含义如为:一维数据列序号为x的值。 keyFormat 可以为 %0-%1-%2,%0-%3-%8-%x...... 例子如下...

91奔跑的蜗牛 635
上一篇: 编写高效Lua代码的方法 - 总结
下一篇: 编写高效Lua代码的方法 - 1 - 基本知识
wihing
博客等级 码龄19年 55粉丝 36原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值