很多人购买异构多核处理器器跑虚拟机是干嘛呀?主要做什么?

虚拟机有什么用处呢? 如果用它装一个LINUX系统会用什么好处呢?_百度知道
虚拟机有什么用处呢? 如果用它装一个LINUX系统会用什么好处呢?
我想装一个LINUX系统 最近接触到虚拟机这个概念我i想知道如果用虚拟机装LINUX和不用虚拟机有什么区别,会有什么好处呢?
希望可以提供一个win7可用的虚拟机下载地址
我有更好的答案
虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。虚拟机的优点:1.演示环境,可以安装各种演示环境,便于做各种例子;2.保证主机的快速运行,减少不必要的垃圾安装程序,偶尔使用的程序,或者测试用的程序在虚拟机上运行;3.避免每次重新安装,银行等常用工具,不经常使用,而且要求保密比较好的,单独在一个环境下面运行;4.想测试一下不熟悉的应用,在虚拟机中随便安装和彻底删除;5.体验不同版本的操作系统,如Linux、Mac等。  终端虚拟化由于其带来的维护费用的大幅降低而受到追捧——如能降低占用空间,降低购买软硬件设备的成本,节省能源和更低的维护成本。它比实际存在的终端设备更加具备性价比优势。但这些并非是教育行业和厂商对虚拟化技术情有独钟的惟一原因。另一方面,我们一般较少提及,那就是:虚拟化技术能大幅提升系统的安全性。
有win7 下可以跑Linux
的虚拟机下载地址吗?谢谢~
现在虚拟机的软件有很多,比较出名的有Vmware workstation (7.0以上支持Windows 7),virtual PC和virtualBox。其中Vmware workstation 功能特别强大,但是有个缺点:收费。virtualBox是由Sun microsystem(太阳微系统)公司推出的免费开源虚拟机,功能也比较强大,现被甲骨文公司(oracle)收购,你可以在oracle公司网站直接下载即可。你要不想下,我传你一个virtual PC是微软自家产品,对自家软件兼容性肯定做的最好。也是很好的虚拟软件,但其下载安装需要操作系统正版验证,不太推荐。希望能帮助到你!!
我是正版系统啊virtual PC可以跑Linux 吗?给个下载地址也行啊我这里有个XP mode就是微软下的
但是貌似就是跑XP的我邮箱是发我邮箱给下载地址都行啊
采纳率:42%
比较通俗的回答(适合没有电脑基础的朋友)虚拟机,顾名思义就是虚拟出来的电脑,这个虚拟出来的电脑和真实的电脑几乎完全一样,所不同的是他的硬盘是在一个文件中虚拟出来的,所以你可以随意修改虚拟机的设置,而不用担心对自己的电脑造成损失,因此可以用来做试验什么的,呵呵,差不多就是这样了,不知道我说的能明白不^_^,简单说就是一句话,虚拟出来的电脑,你干什么都行。 现在说一下虚拟机的软件,主要是两中,Virtual PC和VMware。软件的选择也是有门道滴,嘿嘿,简单来说,VPC的设置很简单,一路next就行了,VM设置相对麻烦一些,不过也不是麻烦很多,但是VM拥有更好的性能,可以说和真实的电脑性能完全一样,还可以用桥接的方式和现在的电脑互连^_^,可以研究的东西就更多了比较专业的回答(适合有一点电脑基础的朋友)在一台电脑上将硬盘和内存的一部分拿出来虚拟出若干台机器,每台机器可以运行单独的操作系统而互不干扰,这些“新”机器各自拥有自己独立的CMOS、硬盘和操作系统,你可以像使用普通机器一样对它们进行分区、格式化、安装系统和应用软件等操作,还可以将这几个操作系统联成一个网络。在虚拟系统崩溃之后可直接删除不影响本机系统,同样本机系统崩溃后也不影响虚拟系统,可以下次重装后再加入以前做的虚拟系统。同时它也是唯一的能在Windows和Linux主机平台上运行的虚拟计算机软件。虚拟机软件不需要重开机,就能在同一台电脑使用好几个OS,不但方便,而且安全。虚拟机在学习技术方面能够发挥很大的作用。 虚拟机(virtual machine)
虚拟机(VM)是支持多操作系统并行运行在单个物理服务器上的一种系统,能够提供更加有效的底层硬件使用。在虚拟机中,中央处理器芯片从系统其它部分划分出一段存储区域,操作系统和应用程序运行在“保护模式”环境下。如果在某虚拟机中出现程序冻结现象,这并不会影响运行在虚拟机外的程序操作和操作系统的正常工作。虚拟机具有四种体系结构。第一种为“一对一映射”,其中以 IBM 虚拟机最为典型。第二种由机器虚拟指令映射构成,其中以 Java 虚拟机最为典型。Unix 虚拟机模型和 OSI 虚拟机模型可以直接映射部分指令,而其它的可以直接调用操作系统功能。在真实计算机系统中,操作系统组成中的设备驱动控制硬件资源,负责将系统指令转化成特定设备控制语言。在假设设备所有权独立的情况下形成驱动,这就使得单个计算机上不能并发运行多个操作系统。虚拟机则包含了克服该局限性的技术。虚拟化过程引入了低层设备资源重定向交互作用,而不会影响高层应用层。通过虚拟机,客户可以在单个计算机上并发运行多个操作系统。微软虚拟服务器2005基于OSI虚拟机结构,主要几种于以下几点:主机操作系统,如 Windows Server 2003,主要控制主机系统。 虚拟机操作系统,如 Virtual Server 2005,包含控制虚拟机的 VMM 虚拟层,为硬件仿真提供软件结构。 每个虚拟机由一组虚拟化设备构成,其中每个虚拟机都有对应的虚拟硬件。 好处:客户操作系统和应用程序可以运行在虚拟机上,而不需要提供任何交互作用的网络适配器的支持。虚拟服务器只是物理以太网中的一种软件仿真设备。 主要是可以装一些软件研究,但是不用装在现在的系统上,系统很干净,想用时启动虚拟机即可。但是如果就是自己平时做一般的事情,不需要装虚拟机,否则启动虚拟机后很耗资源。 而现在一些服务器比较流行装虚拟机,这样可以充分利用服务器,1台物理服务器可以变成好几台服务器,互不影响。
有win7 下可以跑Linux
的虚拟机下载地址吗?谢谢~
虚拟机就是让你的系统多出一个系统来 而且是以窗口形式 比如说你打斗地主 需要串通 那么你本机开一个 虚拟机也开一个 那么两个可以同时打 只不过一个在外面 一个在窗口里 两个互不影响
能用它来装系统吗?有什么好处
可以啊 虚拟机就是一个独立的系统 就相当于一个软件 比如说QQ程序 只不过这个软件是一个独立的系统
不如把linux系统安装在硬盘上再在linux下使用virtualbox安装windows系统。
1条折叠回答
为您推荐:
其他类似问题
您可能关注的内容
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。Lua 社区最近的一件大事是 Lua 5.4 的 work1 版本发布了。
这次的首发版本中引入了一个试验性的新特性,用来解决将 nil 放入数组的问题。因为是实验性特性,所以开发组决定默认关闭,必须在编译源代码的时候定义 LUA_NILINTABLE 这个宏才能开启。注意:默认是不开启的,后面的讨论都以这个为基础。
在邮件列表的讨论中,有不少人引入了不必要的激烈情绪,反对这种影响兼容性的改变。 Roberto 同学看起来是生气了,用全大写字母又重新强调了一次。当然虽然也有人不仔细阅读就评论,也比充斥在网络的大部分地方的喷子要强得多。
我觉得阅读整个讨论能加深对 Lua 语言的理解,非常有价值。这里做一点记录。毕竟,深入学习任何东西都回避不了了解其历史。每次 Lua 的版本升级的预发阶段,都会引入一些有趣的东西,大多数又会在正式版本发布前删掉。说明 Lua 的开发团队在语言设计上是及其谨慎的,我们要追寻这些历史痕迹,也只能从这些讨论中发掘了。
对于不太熟悉 Lua 的同学来说,我们先简单介绍一下这个特性的缘由。
前段为 3d engine 写的向量运算库小伙伴在用,提了很多意见,所以这段时间一直在改进。
一开始觉得逆波兰表示法的运算表达式不太习惯,觉得需要绕个弯想问题,希望做一个表达式编译的东西,但是用了几天后,又觉得其实不是什么大问题,习惯了就好了。
但心智负担比较大的地方是那个 id 的正负号约定,也就是生命期管理。我想了一下,人为的去管理生命期,有些对象是要长期持有的,有些对象只在当前渲染帧使用,在使用的时候严格区分它们不太现实。
一开始的版本,我需要使用者在计算表达式中用一个 mark 'M' 指令,把一个临时对象转换成一个持久对象,这极大的增加了使用者的负担。尤其是更新一个对象的时候,需要先解除老对象的持久状态,再 mark 新生成的对象。使用的时候需要一直考虑这个对象是不是要更新,用起来太困难了。虽然有强检查,不会把程序弄混乱,但是稍不注意就会报告运行时错(对象 id 失效)。
今天,我做了极大的调整,去掉了之前 mark 语义,增加了引用语义。
lua 从 5.2 开始,简化了 5.1 中的模块管理方式,然后一直保持到现在这个样子。
模块用 require 加载,同名模块在一个 vm 中只加载一次,第 2 次开始会返回上次加载的结果。加载模块时会利用 package.path 或 package.cpath 中定义的字符串模板,把模块名转换为文件名,依次尝试打开文件。
我在新项目中,由于整合了不少模块,感觉现有的这套机制有点点不够用。所以我做了一点点小改动,支持了类似 python 的模块管理那样的相对机制。当在一个模块中 require 另一个模块时,会先尝试加载相对路径上的模块,再尝试绝对路径。这样可以方便我们集成独立开始的模块,并放在独立的名字空间中。也方便给模块内置测试子模块。
例如,我独立开发了一个叫 foobar 的模块,它自己有一个子模块叫 foobar.baz ,在集成到系统中时,我希望把它们一起放在 common 名字空间下。使用的时候可以用 require "common.foobar" 来引用。
如果直接用 lua 原生的模块管理机制,我需要修改 foobar 主模块的代码,把里面的 require "foobar.baz" 改成 require "common.foobar.baz" 。同理,如果我不满意 foobar 这个名字,想换名也很麻烦。
如果用纯 lua 来做向量/矩阵运算在性能要求很高的场合通常是不可接受的。但即使封装成 C 库,传统的方法也比较重。若把每个 vector 都封装为 userdata ,有效载荷很低。一个 float vector 4 ,本身只有 16 字节,而 userdata 本身需要额外 40 字节来维护;4 阶 float 矩阵也不过 64 字节。更不用说在向量运算过程中大量产生的临时对象所带来的 gc 负担了。
采用 lightuserdata 在内存额外开销方面会好一点点,但是生命期管理又会成为及其烦心的事。不像 C
中可以使用栈作临时储存,C++ 中有
RAII 。且使用 api 的时候也会变得比较繁琐。
我一度觉得在 lua 层面提供向量运算的基础模块是不是粒度太细了。曾经也想过许多方法来改善这方面。这两天实践了一下想了有一段时间的方案,感觉能初步满意。
首先,我们在 2011 年底开创的简悦被阿里巴巴文化娱乐集团全资收购了。原来简悦的全套班底转型为阿里大文娱游戏事业群。
当收购的事情尘埃落定,我发现可以从新的视角来看待未来,重新设计制作一款 3d 引擎这件事可以重新启动了。在简悦一直想做而做不了这件事,是因为没有余力,必须优先考虑产品盈利;而对于阿里来说,投入资源来做这样一件短期没有收益,但长远看来却很有意义的事是很自然的。
世面上已经有了很多优秀的 3d 游戏引擎,比如目前最为流行的 Unity 和口碑优异的 Unreal ,还有许多品质精良的开源引擎,再从头做一个又有什么意义?
我是这么看这个问题的。
Unity 和 Unreal 固然优秀,但是它们在设计之初并没有把移动设备作为核心平台来考虑。发展历史悠久,固然细节上的完善是后来者无法比拟的,但也存在很多历史包袱。尤其是移动平台上需要特别考虑内存紧致、节约能耗,更胜过运行的更快、效果更华丽。
另外,就国情而言,我们需要的移动游戏需要有更弹性的资源管理以及更新方案,这一直是 Unity 的弱项。Unity 作为一个闭源引擎,很难让使用者做出根本改进。
我们已经和 Unity 达成了合作,购买了全部源码。现在公司也成立了专门的团队自己维护 Unity 源码对其他产品团队做技术支持。在这种情况下,重新抄一个 Unity 没有意义:有什么需求,我们完全可以在 Unity 源码的基础上做开发。所以我要的是一个全新的东西。
最近在用 Lua 实现一个 ECS 框架,用到了一些有趣的 Lua 语法技巧。
在 ECS 框架中,Component 是没有方法只有数据的,方法全部写在 System 中。Entity 本身仅仅是 Component 的组合,通常用一个 id 表示。
但实际写代码的时候,使用面向对象的语法(用 Lua 的冒号 这个语法糖)却是比较自然的写法。比如我们在操作一个 Component 数据的时候,用 component:foobar() 比用 foobar(component) 要舒服一些。好在 Lua 是一门非常动态的语言,我们有一些语法技巧在保持上面 ECS 原则的前提下,兼顾编码的书写体验。
最近在 windows 做开发比较多,lua 原生库使用的都是 C 标准库中的函数,比如文件操作就是用的 fopen 打开文件。这对 unicode 支持的很糟糕。我希望所有和文件名打交道的地方都使用 utf-8 编码,所以今天花了一点时间实现了这么一个库。
我把 lua 原生库中和文件名有关的 api 都重新实现了一遍,包括了:loadfile , dofile , os.rename , os.remove , os.execute, os.getenv , 以及 io.open 。除了 require 都可以在接口上使用 utf-8 字符串了。这里 require 是偷懒没支持 :)
前两年有同学给我推荐了
这个库,第一眼被它吸引是它的口号:"Bring Your Own Engine/Framework" style rendering library 。这动不动就说自己是 3d engine 的时代,好好做好一个渲染库,仅仅做好渲染库,是多难得的一件事情。
今年国庆节的时候,偶然间我又翻到这个仓库,居然作者一直在更新。坚持了五年,一直在维护这么个小玩意,让我对这个项目多了点信心。节后我饶有兴趣的研究了一下它的代码。
现在我觉得,这个库的设计思想非常对我的胃口,核心部分几乎没有多余的东西:数据计算、平台 API 支持、数据持久化格式支持、等等都没有放在核心部分。它仅仅只做了一件事:把不同平台的图形 API :Direct X 、OpenGL 等等整合为一套统一的接口,方便在此基础上开发跨平台的 3d 图形程序。不同平台的 3d api 的差异,正是 3d 游戏开发中最脏最累的活了。
昨天我们一个项目发现了一处死循环的 bug ,经过一整晚的排查,终于确认是 lua 5.3.4 的问题。
起因是最近项目中接入了我前段时间写的一个库, 。它的原理是将数据表先转换为 C 结构,放在一块连续内存里。在运行时,可以根据需要提取出其中用到的部分加载都虚拟机中。这样做可以极大的提高加载速度。项目在用的时候还做了一点点小修改,把数据表都设置成 weaktable ,可以让暂时不用的数据项可以回收掉。
正式后面这个小修改触发了 bug 。
最近同事碰到的一个需求:需要频繁把一组数据在 skynet 中跨网络传递,而这组数据实际变化并不频繁,所以做了大量重复的序列化和传输工作。
更具体一点说,他在 skynet 中设计了一个网关节点,这个网关服务可以负责把一条消息广播给一组客户端,每个客户端由内部的一个 uuid 串识别,而每条消息都附带有客户端 uuid 列表。而实际上这些 uuid 列表组有大量的重复。每条广播消息都重复打包了列表组,且列表组有大量重复信息。
一开始我想的方法是专门针对这个需求设计一组协议,给发送过的数据组编上 id ,然后在发送方和接收方都根据 id 压缩通讯数据。即,第一次发送时,发送全量信息,之后再根据数据变化发送差异;如果完全没有变化,则只需要发送 id 。
之后我想,能不能设计一种较为通用的差异同步方法,可以在跨节点传递数据组的时候,避免将相同的数据重复传输,而采用差异同步的方法同步对象。
又一篇谈 Lua debugger 的 blog 了。但这次,并不是我的个人作品 :) 。
去年底我写了
。正如我的 blog 中所写:“不过期待它短期内发展成为一个图形式的漂亮交互调试器可能有点不现实,除非做前端的朋友有兴趣来完善它。”
ok 。这次,真的有人来完善它了。
我公司的前端大神突然对实现一个 lua debugger 产生了兴趣。他觉得既然 chrome 可以用来调试 javascript ,那么魔改一下后,调试 lua 也完全没有问题。利用几个月的业余时间,他完成了这么个东西:
ps. 不愧是做前端出身啊,开源项目的主页比 skynet 好看多了。
今天试了一下一个想法:绕过 lua 提供的 C API 直接去访问 lua 的表结构,提供在性能及其重要的环境高效访问数据结构的方法。
例如:我们需要在 lua 和 C 中共享一个 vector 3 结构,有两种实现方法:一、把 C struct 实现为 lua 中的 userdata ,然后给 userdata 加上 metatable 以供 lua 中访问内部数据;二、在 lua 中使用一个 table 实现这个 vector3 结构,类似 { x = 0.0 , y = 0.0, z = 0.0 } 这样;然后在 C 里通过 c api (lua_rawget/lua_gettable/lua_getfield) 来访问里面的数据。
前一种方法会导致在 Lua 中访问成本加大、而后一种方法增加的是 C 中访问数据的成本。如果我们只在少数性能敏感的地方通过 C 去操作数据结构,那么第二种方法看起来更简单灵活一些。这样,不需要 C 介入的地方,是没有额外开销的。毕竟、通过 metamethod 索引 userdata 的成本比直接索引一个普通的 table 要重的多。
这两个月,我的主要工作是跟进公司内一个 MMORPG 项目,做一些代码审查提出改进意见的工作。
在数月前,项目经理反应程序不太稳定,经常出一些错误,虽然马上就可以改好,但是随着开发工作推进,不断有新的 bug 产生。我在浏览了客户端的代码后,希望修改一下客户端的 UI 框架以及消息分发机制等,期望可以减少以后的 bug 出生概率。由于开发工作不可能停下来重构,所以这相当于给飞行中的飞机换引擎,做起来需要非常小心,逐步迭代。
工作做了不少,其中一个小东西我觉得值得拿出来写写。
我希望 UI 部分可以严格遵守 MVC 模式来实现。其实道理都明白,但实际操作的时候,大部分人又会把这块东西实现得不伦不类。撇开各种条条框框,纸上谈兵的各种模式,例如 MVC MVP MVVM 这些玩意,我认为核心问题不在于 M 和 V 大家分不清楚,而是 M 和 V 产生联系的时候,到底应该怎么办。联系它们的是 C 还是 P 或是 VM 都只为解决一个问题:把 M 和 V 解耦。
昨天在 review 我公司一个正在开发的项目客户端代码时,发现了一些坏味道。
客户端框架创建了一个简单的对象系统,用来组织客户端用到的对象。这些对象通常是有层级关系的,顶层对象放在一个全局集里,方便遍历。通常,每帧需要更新这些对象,处理事件等等。
顶层每个对象下,还拥有一些不同类别的子对象,最终成为一个森林结构,森林里每个根对象都是一颗树。对象间有时有一些引用关系,比如,一个对象可以跟随另一个对象移动,这个跟随就不是拥有关系。
这种设计方法或模式,是非常常见的。但是在实现手法上,我闻到了一丝坏味道。
很多使用 Unity3D 开发的项目,都不太喜欢 C# 这门开发语言,对于游戏开发很多人还是更喜欢 Lua 一些。而 Lua 作为一门嵌入式语言,嵌入别的宿主中正是它说擅长的事。这些年,我见过许多人都做过 U3D 的 Lua 嵌入方案。比如我公司的阿楠同学用纯 C# 实现了一个 Lua 5.2 (用于在 U3D web 控件中嵌入 Lua 语言的 UniLua );还有 ulua slua wlua plua xlua ... 数不胜数。我猜测,a-z 这 26 个字母早就用完了。
上面提到的项目的作者不少是我很熟悉的朋友,我们公司现在的 U3D 游戏也由同事自己实现了一套差不多的东西。所以我曾了解过这些方案。但我一直觉得这些方案要么做的过于繁琐,要么有些细节上不太完备,总是手痒想按自己的想法搞搞看。
Mono 和 C 通讯使用 P/Invoke ,用起来不算麻烦,但是要小心暗地里做的 Marshal 的代价,特别是对象传递时装箱拆箱的成本。Lua 和 C 通讯有一套完善的 C API ,但完全正确使用并不容易。核心难点是 Mono 和 Lua 各有一套自己的异常机制,让它们协调工作必须很小心的封装两个语言的边界,不要让异常漏出去。 。
我认为简单且完备的 Mono / Lua 交互方案是这样的:
很多项目采用 lua 的一大原因是 lua 可以方便的做热更新。
你可以在不中断进程运行的情况下,把修改过的代码塞到进程中,让随后的过程运行新版本的代码。这得益于 lua 的 function 是 first class 对象,换掉代码不过是在让相应的变量指向新的 function 对象而已。
但也正因为 lua 的这种灵活性,想把热更新代码这件事做的通用,且 100% 做对,又几乎是不太可能的。
首先,你很难准确的定义出,什么叫做更新,哪些数据需要保留,哪些需要替换成新版本。光从源代码和运行时的元信息上去分析是远远不够的。
lua 只有一种通用数据结构 table ,这方便了我们做数据更新;但同时也制造了一些模糊性难题。比如,如果在代码中有一些常量配置数据表,写死在源代码中,通常你是希望跟着新版本一起更新的;而有一些表,记录着运行时的状态,你又不希望在代码更新后状态清空。
所以一般做热更新方案的时候,都会人为加一些约束,在遵循约束条件的前提上,尽量让更新符合预期。
最近一段时间在帮公司一个项目组的客户端 review 代码。
我们的所有项目,无论渲染底层是用的 ejoy2d 还是 Unity3d ,实际开发的时候都基本是使用 lua 。所以开发人员日常工作基本是在和 Lua 打交道。
虽然我个人挺反感围绕着调试的开发方式,也就是不断的在测试、试错,纠正的循环中奔波。我认为好的程序应该努力在编写的过程中,在头脑中排错;在预感到坏味道时,就赶快重写。而坏味道通常指代码陷入了复杂度太高的境地,无法一眼看出潜在的问题。对付复杂度最好的武器是简化代码,而非调试器。
在真正遇到 bug 时,应该仔细浏览代码,设想各种出错的可能。而不是将错误的代码运行起来,查看运行中的状态变化。
话说回来,看到项目组的同学真的碰到 bug 时,不断的启动 Unity 客户端,把时间浪费在等待那几行 debug log 上,我觉得效率还是很低。必要的调试工具应该能提升一些开发效率的。
lua 官方提供了完善的 debug api 可以查询所有的信息;但并没有一套官方的调试工具。我都不记得是第几次写调试工具了。至少在这个 blog 上就记录了好几次。 。
今天有同学跟我讨论了一下最近发现的一个 bug ,我觉得挺有意思的。
需求是这样的:
我们的系统中,有一些数据是从外存(数据库)加载进来的,由于性能考虑,并不需要每次修改这些数据就写回外存。希望在数据变冷后,定期落地即可。
典型的场景是一个 cache 模块,cache 的是一些玩家的业务数据,可以通过 uuid 从数据库索引到。一旦业务需要访问玩家数据,cache 模块会从数据库加载对应数据,然后把数据表交出去。当业务再次需要这些数据的时候,cache 模块一旦发现数据存在于 cache 中,就直接交给玩家。
cache 模块还希望在数据很久没有被业务访问时,将这些数据写回数据库。
我们的系统是基于 lua 构建的,数据 cache 模块和修改这些数据的逻辑在同一个 vm 里。难点在于,修改数据的业务逻辑是可以长期持有数据的,cache 模块需要正确感知这点。
今天在公司群里,Net bug 同学提出了一个问题,围绕这个问题大家展开了一系列讨论。讨论中谈及了 lua 中的一个常见的模式:property table ,我觉得挺有意思,记录一下。
最初的问题是:当一个对象的某些属性并不常用,希望做惰性初始化的话,应该怎么实现。
我认为,property table 是一个很符合这个案例的常见模式。
比如,对象 f 有三个可能的成员 a b c ,我们可以不把 f.a f.b f.c 记录在 f 这个 table 里,而是额外有三张大表,a b c 。利用 metatable ,可以在访问 f.a 的时候,实际访问的是 a[f] 。也就是说,所有同类对象的 a 属性,都是从 a 这张表里访问的。
a 这张表的 key 就是对象,value 是对象对应的 a 属性值。
Lua 的 table 可以做数组用,但是前提是数组里不能有空洞。也就是不能在数组里保存 nil ,否则取长度和迭代的行为都是不确定的。
能不能用比较小的额外代价在 Lua 中实现一个支持空洞的数组呢?
首先,我们定义一下,带空洞的 array 的正确行为应该是怎样的:
数组只能用正整数做 key ,设置其它 key 会抛出 error 。
可以用 pairs 迭代数组,和普通的 table 一样,迭代器会跳过那些值为 nil 的键值对。但要求迭代器一定从 1 开始从小到大按次序迭代。
用取长度 (#) 操作符,可以正确的返回数组的大小,即最大一个正整数 key 。
ipairs 的行为不变,会在第一个 nil 处停下来。
在 Lua 5.2 之后的版本,约定了在元表中可以给出一个 __pairs 方法,而 lua 的基础库 pairs 会使用这个元方法来迭代一个对象。
Lua 5.3 之后的版本,取消了 lua 5.2 中的 __ipairs 约定,而统一使用 lua_geti 来访问整数为索引的数组。
可惜的是,许多 lua 序列化库对此支持的并不好。今天我在改进 bson 的序列化库时,重新考虑了这个问题,看看这个序列化过程怎么做,才能更好的支持 lua 5.3 以后的约定。
今天花了一天尝试给 lua vm 做了一点优化:
现在 lua 的函数原型里保留有一张常量表,引用了 string ,number ,nil ,boolean 类型的常量。
table 是不能为常量的,所以当你想迭代一个常量数组的时候,
for _, v in ipairs { "one", "two", "three" } do
其实每次都会临时构建一张表,并依次插入 "one", "two", "three" 。
或者你想返回一个常量构成的表:
function foo()
return { x=1, y=2 }
每次 foo 函数都会为返回值重新构建 table 。
无论是客户端还是服务器,把 lua 作为嵌入语言使用的时候,都在某种程度上希望把 lua 脚本做多线程使用。也就是你的业务逻辑很可能有多条业务线索,而你希望把它们跑在同一个 lua vm 里。
lua 的 coroutine 可以很好的模拟出线程。事实上,lua 自己也把 coroutine 对象叫做 thread 类型。
最近我在反思 skynet 的 lua 封装时,想到我们的主线程是不可以调用阻塞 api 的限制。即在主干代码中,不可以直接 yield 。我认为可以换一种更好(而且可能更简洁)的封装形式来绕过这个限制,且能简化许多其它部分的代码。
下面介绍一下我的新想法,它不仅可以用于 skynet 也应该能推广到一切 lua 的嵌入式应用(由你自己来编写 host 代码的应用,比如客户端应用):
skynet 本质上只是一个消息分发器,以服务为单位,给每个服务一个独立的 id ,可以从任意服务向另一个服务发送消息。
在此基础上,我们在服务中接入 Lua 虚拟机,并将消息收发的 api 封装成 lua 模块。目前用 lua 编写的服务在最底层只有一个入口,就是接收并处理一条 skynet 框架转发过来的消息。我们可以通过 skynet.core.callback (这是一个内部 API ,用 C 编写,通常由 skynet.start 调用)把一个 lua 函数设置到所属的服务模块中。每个服务必须设置,且只能设置一个回调函数。这个回调函数在每次收到一条消息时,接收 5 个参数:消息类型、消息指针、消息长度、消息 session 、消息来源。
消息大致分两大类,一类是别人对你发起的请求,一类是你过去对外的请求收到的回应。无论是哪类,都是通过同一个回调函数进入。
在实际使用 skynet 时,你可以直接使用 rpc 的语法,向外部服务发起一个远程调用,等对方发送了回应消息后,逻辑接着走下去。那么,框架是如何把回调函数的模式转换为阻塞 API 调用的形式呢?
这多亏了 lua 支持 coroutine 。可以让一段代码运行了一半时挂起,在之后合适的时候在继续运行。
今天同事提了个需求,他希望可以给部分 lua 代码(由策划编写)做一个沙盒关起来。在 lua 里做沙盒很容易,只需要控制函数的环境就可以了。不过另一个附加需求是,这些代码还可以直接利用 require 加载。
而我们又不想去修改系统的
api 接口,那么怎么做到这点呢?
首先, 我希望使用的时候看起来像这样:
local xxx = require "xxx" (myEnv)
和传统的 require 用法不同,可以在后面追加一个参数 myEnv 。这样的话,每次 xxx 模块被 require 时,它其实被重复运行一次,但会绑定不同的 _ENV 。
其次,既然模块会被反复初始化,那么我们甚至还可以约定,每个这种沙盒封装的模块还可以接收 require 的传入的额外参数。
lua 中 40 字节以下的字符串会被内部化到一张表中,这张表挂在 global state 结构下。对于短字符串,相同的串在同一虚拟机上只会存在一份。
在 skynet 中,有大量的 lua vm ,它们很可能加载同一份 lua 代码。所以我之前改造过一次 lua 虚拟机,[让它们可以共享 Proto] 。这样可以加快多个虚拟机初始化的速度,并减少一些内存占用。
但是,共享 Proto 仅仅只完成了一半的工作。因为一段 lua 代码有一很大一部分包含了很多字符串常量。而这些常量是无法通过共享 Proto 完成的。之前的方案是在
clone function 的时候复制一份字符串常量。
或许,我们还可以做的更进一步。只需要让所有的 lua vm 共享一张短字符串表。
Lua 是一门嵌入式语言,和 host 的联动非常重要。Lua 使用 userdata 来保存 host 里的数据,userdata 非常强大,可以有 metatable 还可以关联一个 uservalue ,可以封装一切 C/C++ 对象,非常强大。但有的时候却稍显不足,似乎缺了点什么,导致一些简单的需求要用很繁琐的方式解决。
有个想法想过很久,今天动了念头用英文写了一遍投递到 lua 邮件列表里去了。
那就是,如果我们可以给 userdata 的值关联一个整数,而不是把 uservalue 关联到 userdata 的对象里那样,可以简化很多事情。
从周末开始, 我一直在忙一个想法。我希望给 skynet 中的 lua 服务定制一个内存分配器。
倒不是为了提升性能。如果可以单独为每个 lua vm 配置一个内存分配器,自己调用 mmap 映射虚拟内存,就可以为独立的服务制作快照了。这样可以随时 fork 出子进程,只保留关心的 vm 的内存快照。主要可以有三个用途:
可以在快照上做序列化,并把结果返还父进程。通常做序列化有一定的时间代价,如果想定期保存的话,这个代码很可能导致服务暂停。
可以利用快照监控检查泄露。定期做快照相比较,就能找到累积的对象。 。
可以在镜像上对快照做一些调试工作而不会影响主进程。
今天有人转了个知乎上的帖子给我看: 。
首先,我不认为 10% 的性能差异能够称的上很大,和 10% 的性能下降相比,程序更清晰稳定、功能更完备(不是指功能多,而是指对各种边界条件处理的更好)要重要的多。毕竟,让 CPU 提升 10% 的性能很容易。
其次,在实际项目中,和简单的测试脚本不同,我很难观察到 10% 的差异。(我们的服务器用过 lua 5.2 和 lua 5.3 两个版本,很难从线上压力上感知到性能差别)。
如果你真的用那些简单的测试脚本做一个比较,lua 5.1 比它的前一个版本 lua 5.0 要慢得多。差别或许比 lua 5.1 到 5.3 还要大。而为什么很少人关心这个,去用回 5.0 呢?说到底还是因为 luajit 导致的 lua 社区的分裂,让 lua 5.1 这个中间版本变成了另一个 lua 而已。
我的硬盘上一直留有从 lua 4.0 开始几乎每个 lua 小版本的源代码副本。而近十年来,一直都在跟进 lua 的源码变迁,所以我对 lua 的每次修改都或多或少有一些印象。下面谈谈我对这个新版本越来越慢的一点个人看法吧。
Lua 作为一门嵌入式语言,提供了完备的 C API 供 Lua 代码和宿主程序交互,当然,宿主语言最好是 C 或 C++ 。如果是其它语言,比如最近两年流行的在 mono 环境嵌入 Lua 另当别论。
正确将 Lua 嵌入是不太容易做对的事情,很多刚接触 Lua 的人都容易犯错误。好在做这种语言桥接工作都是项目开始阶段的设计者做的,不必人人学会,所以只要有熟悉 Lua 的人来搞,犯错误的危害不会太大。而且即使做的有问题,日后修改也比较容易。这篇 blog 主要就是谈谈,最容易做错的位置,和一些正确(但看起来麻烦)的实现方法。
最容易忽略的是 Lua 中 error 的处理。
Lua 中叫 error ,再其它语言中叫 exception ,后面姑且全部称为异常吧。
如果你认真读过 。 就会发现,在所有 C API 那里,都注明了这个 API 是否会抛出异常。比方说 lua_tostring 就标注的是 [-0, +1, e] ,有可能抛出异常(是不是和你的直觉不同?);但 lua_pushlightuserdata 则不会。
Lua 的异常应该由 lua_pcall 或 lua_resume 来捕获,所以当你调用 C API 的时候,应该确保在 C 的调用层次上,处于某次 lua_pcall 或 lua_resume 中。所以,即使是常见的创建 Lua 虚拟机的简单几行代码,都有可能写错。比如:
lua_State *L = luaL_newstate();
luaL_openlibs(L);
这样写就是考虑不周的。因为 luaL_openlibs(L) 可能抛出异常,这样便没有捕获它。
写这个东西的起源是,前段时间我们的平台组面试了一个同学,他最近一个作品叫做
。面试完了后,他专门找我聊了几个小时他的这个项目。他的核心想法是基于 luajit 做一个 web server ,和 ngx_lua 类似,但撇开 nginx
。当时他给我抱怨了许多 luajit 的问题,但是基于性能考虑又不想放弃 luajit 而转用 lua 。
我当时的建议是,不要把 lua/luajit 作为嵌入语言而自己写 host 程序,而是想办法做成供 lua 使用的库。这样发展的余地要大很多,也就不必局限于用户使用 lua 还是 luajit 了。没有这么做有很多原因是设计一个库比设计一个 host 程序要麻烦的多,不过麻烦归麻烦,其实还是可以做一下的,所以我就自己动手试了一下。
Lua 的多任务库有很多,有兴趣的同学
最近,我们的合作方 陌陌 带了他们的一个 CP 到我们公司咨询一下 skynet 做 mmo 游戏项目中遇到的一些问题。因为他们即将上线一款 MMO ,在压力测试环节暴露了许多问题。虽然经过我们的分析,有很多问题出在他们的压力测试程序本身编写的 bug ,但同时也暴露出服务器的设计问题。
核心问题是,他们在实现 mmo 服务器时,虽然使用了 skynet 框架,但却把所有的业务逻辑都放在了同一个 lua 服务中,也就是一切都运行在一个 lua states 里。这样,几乎就没能利用上 skynet 原本想提供的东西。压力是一定存在的。
我花了一下午探讨了应该如何设计一个 MMO 的服务器。下面记录一下:
在 skynet 中,有一个叫 monitor 的内部模块,它会监测是否有服务可能陷入了死循环。
工作原理是这样的:每次处理一个服务的一个消息时,都会在一个和服务相关的全局变量处自增 1 。而 monitor 是一个独立线程,它每隔一小段时间(5 秒左右)都检测一下所有的工作线程,看有没有长期没有自增的,若有就认为其正在处理的消息可能陷入死循环了。
而发现这种异常情况后,skynet 能做的也仅仅是输出一行 log 。它无法从外部中断消息处理过程,而死循环的服务,将永久占据一个核心,让系统整体性能下降。
采用 skynet 的 kill 指令是无法杀掉死循环的服务的。
当服务用 lua 编写时,我们则有可能做多一点工作。
一直有人问,如何调试 skynet 构建的服务。
我的简单答案是,仔细 review 代码,加 log 输出。长一点的答案是,尽量熟悉 skynet 的构造,充分利用预留的监控接口,自己编写工具辅助调试。
之前的好多年,我也写过很多 lua 的调试器,这里就不一一翻旧帖了。今天要说的是,我最终还是计划加入 1.0 正式版的调试控制台。
也就是单步跟踪调试单个 lua coroutine 的能力。这对许多新手来说是个学走路的拐杖,虽然有人一辈子都扔不掉。
最近想给 skynet 加一个在线调试器,方便调试 Lua 编写的服务。
Lua 本身没有提供现成的调试器,但有功能完备的 debug api 。通常、我们可以在代码中插入 debug.debug() 就可以进入一个交互环境,输入任何 Lua
指令。当然,你也可以在 debug hook 里调用它。
但这种交互方式有一个缺点:lua 直接用 load 翻译输入的文本,转译为一个 lua 函数并运行。这注定了这个输入的代码中不能直接访问到上下文的局部变量和 upvalue 。
如果想读写上下文中的局部变量或 upvalue ,还得使用 debug.getlocal 等函数。这无疑是相当麻烦的。
最近在慢慢把公司的几个项目从 Lua 5.2 迁移到 Lua 5.3 ,为发布 skynet 1.0 alpha 版做准备。
在更新代码时发现了一些注意点,罗列一下:
Lua 5.3 去掉了关于 unsigned 等的 api ,现在全部用 lua_Integer 类型了。这些只需要换掉 api ,加上强制转换即可。通常不会有什么问题。
最需要细致 review 代码升级的是和序列化相关的库。在 skynet 里是序列化库、sproto、bson 等。我们还用到了 protobuffer ,也和序列化有关。
这是因为,Lua 5.3 提供了整型支持,而序列化工作通常需要区分浮点和整数分开处理。json
这种文本方式则不需要,同样还有 redis 的通讯协议也是如此。
过去判断一个 number 是浮点还是整数,需要用 lua_tonumber 与 lua_tointeger 各取一份做比较。虽然到了 Lua 5.3 这种代码理论上可以不用改动,但正确的方法应该是使用 lua_isinteger 。
Lua 5.3 正式发布了。
我的三个计划就需要开动了。
计划一:把文档重新翻译一遍。
八年前,。今天我想再从头做一次翻译工作,借这个机会可以理一遍(以及更新)和 Lua 相关的知识。这次,我选择在 github 上做这个工作,而不是闷头搞完了再发布。
这个项目在
,如果你只是想阅读最终的手册,可以访问。如果发现了错别字,不用给我留言,你只需要在 github 上提个 PR ,我会合并的。
今天下午开始的,目前译完了
、、和的很少一部分。我估计需要全职至少 3 个工作日才能全部译完。这次我选择尽量把英文术语翻译成中文,对于我按个人喜好选择的译词,我专门列在一个中。
在 2015 年的新年里,。
如果回顾 Lua 5.2 的发布历史,Lua 5.2 的 final 版是在 rc8 之后的 2011 年 12 月 17 日发布的,距离 rc1 的发布日 2011 年 11 月 24 日过去不到 1 个月。我们有理由相信正式版不远了。( 5.3 的 rc1 是 2014 年 12 月 17 日发布的)
这次升级对 Lua 语言层面的影响非常的小,但新增加的 int64 支持,以及 string pack 、utf8 库对开发帮助很大。所以我强烈建议正在使用 Lua 5.2 的项目尽快升级到 5.3 。相对而言,当初 5.1 向 5.2 升级的时候就痛苦的多(去掉了 setfenv ,增加了 _ENV)。
我计划在 Lua 5.3 正式发布后,将 skynet 内置的 Lua 版本升级到 5.3 ,然后着手进行 skynet 1.0 的发布工作。
我相信至少在国内的游戏策划圈, Excel 是每天必不可少的存在。倒不是因为要用它制作数值表格,一切文档最终都一定是用 Excel 写的。但作为一个程序员,我相当的痛恨 Excel 文件,就好像我当初痛恨 word 一样。只有几个字就不要保存成 doc 文件啦,可现在已经没有人用 word 了,大家全转去 Excel 了。如果有可能,策划一定愿意在单元格里写脚本的,这样可以将重点标红。
提取 Excel 中的文字信息并不复杂,但真正的麻烦在于 Excel 文件对版本管理工具是极不友好的。甚至你打开一次 Excel 文件再保存关闭,也会生成一个完全不同的新版本。这是因为,文件中记录了最后修改的时间(是的,Excel 不信任文件系统里的时间);还有激活的单元格是哪一个。在这种环境下,多人协作的版本控制工具用起来绝对是一个悲剧。
我大概花了一周时间来试图解决一系列问题。结果不算成功,也不算失败。这里记录一下上周踩过的坑。
问题源于我们的项目中,策划把一切他们能生产的东西都记在了诸多的 excel 表格里。当然,和上世纪的程序员一样,大家都尽量自己维护自己的那块文件,所以即使在版本管理工具下,也基本没有冲突。但是总有那么 1% 的机会,几个人会修改同一张表格的,尤其在项目压力大时,往往实现功能的程序也会打开表格对里面的数据做一些修改。在版本控制工具下,冲突就在所难免了。尤其是我们刚刚让策划从 svn 迁移到 git 下,git 的工作流的复杂性很容易让策划的脑子不够用了(实际上受 Excel 文件格式限制,他们也只需要一个版本备份工具,其它本来就是多余的)。我开始动念头来解决问题。
首先,xlsx 文件其实是一个标准 zip 压缩包,里面打包了一系列 xml 文件。如果仅仅是需要一个文本格式,那么只需要把包解开,用一种非压缩的形式重新打包即可。
对于一些嵌入的图片,只需要用 base64 编码。由于嵌入表格的图片多半不会修改,所以并不会造成版本间的差异。
一开始,我以为这项工作两小时就能搞定,事后发现,太天真了。
我写了一个 lua 的小程序,可以读出 zip 包里的文件,对文件名排序,然后按文件名/内容的次序依次把文件连在一起形成一个大文本文件(其中的2进制内容使用 base64 编码)。这样处理后,xlsx 文件基本就是一个文本文件了。为了对版本管理工具友好,我对 xml 里的标签后增加了适当的分行。这样处理以后,版本管理工具基本能识别出表格数据每个版本的差异。
第2步,可以动手消除一些对版本有影响却对我们没有意义的数据段。比如文件的最后修改时间、激活的单元格等。这样、如果打开一个 excel 文件,保存后就不会产生差异。
那么,这是一个新的文件格式。怎么让 Excel (或 wps 等兼容产品)打开它编辑呢?
虽然第一反应是给 excel 写一个插件。但我知道拿不是一两个小时可以搞定的。所以我选择了一条弯路。写了一个脚本,可以生成一个临时目录/文件,在用户想打开一个自定义格式文件时,先转换为标准的 xlsx 临时文件,让关联的软件(excel 或 wps 等)编辑它。我们可以监控这个文件的变更时间,来即使把临时文件转换回去。当这个临时文件可写时,就表示已经停止编辑这个文件了(excel 对打开的文件有文件锁定)。这时,可以删除临时文件。
让自定义文件格式关联到这个脚本(我用 lua 编写的十多行程序),策划就可以直接双击自定义格式文件编辑了。
skynet 目前的 api 提供的偏底层,由于一些历史原因,某些 api 的设计也比较奇怪。(比如 skynet.ret 是不对返回数据打包的)
我想针对一些最常见的应用环境重新给出一套更简单的 api ,如果按固定模式来编写 skynet 的内部服务会简单的多。
这就是这两天实现的 snax 模块。今天我已经将其提交到 github 的 snax 分支上,如果没有明显的问题,将合并入主干。
snax 仅解决一个简单的需求:编写一个 skynet 内部服务,处理发送给它的消息。snax 并不会取代 skynet
原有的 api ,只是方便实现这类简单需求而已。
有时候我们的项目需要大量的配置表(尤其是网络游戏) 。因为主要用 lua 做开发,我们倾向于直接用 lua table 保存这些配置常量。
海量的数据有两个问题:
这些配置数据在运行期是不变的,但树型结构复杂,放在 lua 虚拟机内会生成大量的 gc object ,拖慢 lua 的垃圾收集器。因为每次扫描都需要把所有配置数据都标记一遍。
在服务器端,我们使用 skynet 框架,会启动数千个 lua 虚拟机。如果每个虚拟机都加载一份配置信息,会带来大量的内存浪费。
Lua 的 API 设计的非常精良,整个 lua 核心库把内存管理都托管给了 lua_Alloc 这个用户注入的函数。任何时候在发生内存不足,lua 的 api 都可以正确处理异常。
考虑一下 lua_newtable 或是 lua_pushlstring 这些 api ,它们都需要创建新的 gcobject ,这些时候如果发生 lua_Alloc 分配不出内存怎么办?这些 api 可都是无返回值的。
lua 的行为是:抛出一个内存错误,如果外界没能捕获这个错误,则触发 panic 函数。
在编写将 lua 嵌入到宿主程序中的一个常见的错误是:
先用 lua_newstate 创建出一个 lua 虚拟机,然后直接调用 luaL_openlibs 等函数初始化它。如果你希望你的代码足够严谨,就必须了解,初始化的过程是有可能遇到内存申请不到的情况的。
正确的做法在 lua 自带的解释器实现中有一个很好的范例:你可以写一个 lua c function ,在里面做后续对 lua_State 的操作。在 lua_newstate 后,立刻 lua_pcall 这个 C 函数,而不是直接调用它。这样,所有的内存异常都会被这次 pcall 捕获住。
btw, 早期版本的 lua 有一个 lua_cpcall 函数,自从 lua 支持 light c function 后就去掉了这个 api 。
在 skynet 这种应用中,同一个系统进程里很轻易的就会创建数千个 lua 虚拟机。lua 虚拟机本身的开销很小,在不加载任何库(包括基础库)时,仅几百字节。但是,实际应用时,还需要加载各种库。
在 lua 虚拟机中加载 C 语言编写的库,同一进程中只会存在一份 C 函数原型。但 lua 编写的库则需要在每个虚拟机中创建一份拷贝。当有几千个虚拟机运行着同一份脚本时,这个浪费是巨大的。
我们知道,lua 里的 function 是 first-class 类型的。lua 把函数称为 closure ,它其实是函数原型 proto 和绑定在上面的 upvalue 的复合体。对于 Lua 实现的函数,即使没有绑定 upvalue ,我们在语言层面看到的 function 依然是一个 closure ,只不过其 upvalue 数量为 0 罢了。
btw, 用 C 编写的 function 不同:不绑定 upvalue 的 C function 被称为 light C function ,可视为只有原型的函数。
如果函数的实现是一致的,那么函数原型就也是一致的。无论你的进程中开启了多少个 lua 虚拟机,它们只要跑着一样的代码,那么用到的函数原型也应该是一样的。只不过用 C 编写的函数原型可以在进程的代码段只存在一份,而 Lua 编写的函数原型由于种种原因必须逐个复制到独立的虚拟机数据空间中。
我们现在的手游完全用 Lua 开发,这就有了调试的需要。
今年曾写过一个,主要是用于服务器开发。服务器程序不适合完全 stop the world 慢慢调试,以输出 log 为主。但现在在客户端,那么一个类 gdb 的调试环境更好一些。
。从网易出来后没带代码,需要用就要重新写了。好在 lua 的 debug 接口非常全,今天花了 2 个小时就重新实现了一个简陋的雏形。
以前我在 blog 写过
,而 Lua 5.2 对这部分代码改动颇多,暂时也没有精力更新这个系列,先挑重点写吧。
Lua 5.2 的 GC 的最大改进是增加了一种叫
generational 的模式,Lua 的官方文档里是这样解释的。
As an experimental feature in Lua 5.2, you can change the collector's operation mode from incremental to generational. A generational collector assumes that most objects die young, and therefore it traverses only young (recently created) objects. This behavior can reduce the time used by the collector, but also increases memory usage (as old dead objects may accumulate). To mitigate this second problem, from time to time the generational collector performs a full collection. Remember that this is an
you are welcome to try it, but check your gains.
根据 Lua 文档中的说法,lightuserdata 比 fulluserdata 要廉价一些。那么,其中的区别在哪里呢?
空间开销上,fulluserdata 是一个 GC 对象,所以比 lightuserdata 要多消耗一点内存,这点内存往往对程序不造成太大的影响。
时间开销上,fulluserdata 在访问它时和 lightuserdata 并无太大区别,它们都只能通过元方法才能在 Lua 中使用。所有 lightuserdata 共用一个元表,不如 fulluserdata 灵活,在元表访问效率上却是几乎相同的。对程序性能有影响的部分在于它们对 GC 环节的开销不同。
fulluserdata 本身是一个 GC 对象,所以在扫描的时候要复杂一些。它可能有附带的 uservalue 需要扫描,但不设置 uservalue 几乎就没有额外的扫描开销了。当 fulluserdata 有 gc 元方法后,就给 GC 流程增加了额外的负担。GC 模块需要额外记录一个链表来串接起所有有 gc 元方法的对象,推迟到 gc 的最后环节依次调用。
对于对延迟相当敏感的游戏程序来说,最容易造成运行过程中瞬间延迟增加,却又很难控制的部分就是 GC 了。所以我们在开发中经常需要关注怎样合理的使用 Lua 避免 GC 的负担过大。
这几天在 lua
和 luajit 的邮件列表上有人讨论 coroutine 的再利用问题。
前几天有个用 skynet 的同学给我写了封邮件,说他的 skynet 服务在产生了 6 万次 timeout 后,内存上升到了 50M 直到 gc 才下降。
这些让我重新考虑 skynet 的消息处理模块。skynet 对每条消息的相应都产生了一个新的 coroutine ,这样才能在消息处理流程中,可以方便的切换出去让调度器调度。诸如 RPC/ socket 读写这些 api 才能在用起来看成是同步调用,却在实现上不阻塞线程。
读源码可知,lua 的 coroutine 非常轻量(luajit 的略重)。但依旧有一些代价。频繁的动态生成 coroutine 对象也会对 gc 造成一定的负担。所以我今天花了一点时间优化了这个问题。
简单说,就是用自己写的 co_create 函数替换掉 coroutine.create 来构建 coroutine 。在原来的主函数上包裹一层。主函数运行完后,抛出一个 EXIT 消息表示主函数运行完毕。并把自己放到池中。如果池中有可利用的旧 coroutine ,则可以传入新的主函数重新利用之。
为了简化设计,如果 coroutine 中抛出异常,就废弃掉这个 coroutine 不再重复利用。为了防止 coroutine 池引用了死对象,需要在主函数运行完后,把主函数引用清空,等待替换。
具体实现 。
ps. coroutine poll 故意没实现成弱表,而是在相应 debug GC 消息时再主动清空。
前几天在做 Hive 的 socket
库的时候, 遇到一个问题很典型,我记得不是第一次遇到了。值得记录一下。
socket 底层有一个 poll 的 api ,通过 epoll 或
kqueue 或 select 取得一系列的事件。用 lua 怎么封装它呢?
一个比较直接的想法是注入一个 callback function ,对于每个事件回调一个 lua 函数。但这容易引起许多复杂的问题。因为回调函数很不可控,内部可能抛出异常,也可能引起函数重入,或是做了一些你不喜欢去做的事情。
如果面面俱到,就会让原本 C/Lua 边界的性能问题更加恶化。
所以,我采用了方案二:把所有事件以及相关数据全部返回,让后续的 Lua 代码去处理 C 层获取的所有事件。
这个方案也容易造成性能问题,那就是临时构件复杂数据结构,对 Lua VM 的 GC 造成的压力。
上个周末我一直在想,经过一年多在
上的开发,我已经有许多相关经验了。如果没有早期 erlang 版本的历史包袱以及刚开始设计 skynet 时的经验不足,去掉那些不必要的特性后的 skynet 应该是怎样的。
一个精简过代码的 skynet 不需要支持
之外的语言和通讯协议。如果某个服务的性能很关键,那么可以用 C 编写一个 Lua 库,只让 Lua 做消息分发。如果需要发送自定义协议的消息,可以把这个消息打包为一个 C 结构,然后把 C 结构指针编码在发送的消息中。
skynet 的内部控制指令全部可以移到一个系统服务中,用 Lua 编写。
跨机支持不是必要的。如果需要在多个进程/机器上运行多份协同工作,可以通过编写一个跨机通讯的服务来完成。虽然会增加一个间接层使跨进程通讯代价更大,但是可以简化许多代码。
广播也不是基础设施,直接用循环发送复制的消息即可。为了必要过大的消息在广播过程中反复拷贝,可以把需要广播的消息先打包为 C 对象,然后仅广播这个 C
对象的指针即可。
唔,我知道有人已经做了 MongoDB 的 lua driver ,比如 。但我不想仅仅是对 C++ API 的封装,而想从协议层做起,这样日后可以方便改为异步模型,也好整合到
这里还有一份 的实现,是从协议层做的封装。但有几个问题,一是依赖 lua-socket 库,二是纯 lua 实现不如 C 库性能好,三是特性没有支持完整。
我。做的过程中发现 c driver 代码质量不高,且特性支持不完整,最终我考虑自己从开始重新做一份。
的官方网站上链接了一个,但是实现的不完整。
我用 C 实现了一个
设计的结构化数据序列化协议,所以有很多设定是为 mongodb 服务的,如果单用于序列化结构化数据,那么那些不一定要实现。但我写这个的最终目的是做一个 lua 的 mongo driver ,所以就实现的比较完整了。
bson 结构中,有一些固定长度的字段,修改它们不必重新编码。这在 mongDB 的通讯协议中非常有用,所以我也加了对应的接口。还有许多特性可能会有用,比如把两个 bson document 连接成一个之类的,等我在写 mongoDB driver 的时候,视情况实现。
2014 : 3 月 12 日补充
因为 mongo 对有些 bson 文档要求 key 的次序, 而 lua 的 table 是无序的。所以增加了 bson.encode_order 按次序打包 document 。
最近思考了给 Lua 写 C 扩展的另一个问题。
我曾经总结过几种 Lua C 库中
。最近想到另一个方案,虽然实现后并没有用到项目里,但值得记录一下。
Lua 没有 RAII ,一切对象的回收是依赖 GC 的。封装 C/C++ 对象则一般用 userdata 。userdata 比较重,作为临时对象使用总觉得有点别扭。比如封装 matrix 对象,如果我们为每个 matrix 对象都生成一个 userdata ,那么一些临时的 matrix 对象就会一直推迟到 GC 发生的时候才回收。而在 C/C++ 这样的语言中,临时对象通常是在离开调用层次时自动释放的。
对于某些 C 和 Lua 混合的业务也有这样的问题。某些较长的业务流程,一部分环节由于性能原因使用 C 来实现,另一部分更适合直接用 Lua 。我们必须用 userdata 来交换中间状态。比如处理一个 C 层次上产生的数据包或 C 结构数据,交由 Lua 处理后,C 对象就没有必要再存在了。但处理过程中,Lua 代码则需要反复引用和处理它。
多数情况下,我们不用太考虑这两者间的差别。但这并不妨碍我去考虑有没有可能在 Lua 中模拟一套栈对象的管理机制。它可能是 GC 系统之外的一种对象生命期管理的选择。
最近听从同事建议想尝试一下 MongoDB 。
前年,图灵的同学送过我一本《》 ,当时我花了两个晚上看完。我所有的认知就是这本书了。我们最近的合作项目
也是用的 MongoDB ,最近封测阶段,关于数据库部分也出过许多问题。蜗牛同学在帮助成都的同学做调优,做了不少工作。总是能在办公室里听到关于 MongoDB 的话题。
做一个 MongoDB 的 Driver 。
Skynet 默认是用 lua 做开发语言的。那么为什么不直接用
因为 skynet 需要一个异步库,不希望一个 service 在做数据库操作的时候被阻塞住。那么,我们就不可能直接把 luamongo 作为库的形式提供给 lua 使用。
一个简单的方法是 skynet 目前对 redis 做的封装那样(当然,skynet 中的 redis 封装也是非阻塞的),提供一个独立的 service 去访问数据库,然后其它服务器向它发送异步请求。如果我直接使用 luamongo 就会出现一个问题:
我需要先把请求从 lua table 序列化,发送给和 mongoDB 交互的位置,反序列化后再把 lua table 打包成 bson 。获得 MongoDB 的反馈后,又需要逆向这个流程。这是非常低效的事情。如果我们可以直接让请求方生成 bson 对象,这样就可以直接把 bson 对象的指针发过到 交互模块就够了( skynet 是单进程模型,可以在服务内直接交换 C 指针)。这就需要我定制一套 lua moogodb 的 driver 了。
最近有份工作是需要把 Lua 中的数据结构以某种特定的格式输出为文本的,所以就用到了
这是个代码生成工作的利器。
可能是用的人不多,所以还略显不完整。用的时候发现一些个小问题,原来以为是 bug,读了源代码后发现是个 feature 。但是觉得这个 feature 不太合理,就上 github 上留言。作者倒挺爽快,马上表示赞同并去掉了。
。这个东西原本是 luajit 的一部分,可好多人确是冲着 ffi 库去用 luajit 的。
luajit 目前尚有不少的局限性,比如内存只能用 32 位寻址,不支持 lua 5.2 的 api 等。另外,从稳定性上来说,也不如原版的 lua 更让人放心。
据我所之,我们合作的 的服务器端就为了 ffi 使用了 luajit ,却担心稳定性问题,把 jit 功能关闭了。
这类项目,未必是稀罕 luajit 的性能,更多的是贪图用 ffi 写 binding 的便捷才启用 luajit 的。
5 月 16 日 注:由于已经收到足够多的简历,所以招聘提前终止,谢谢大家的热情。
招聘网络游戏服务端开发人员一名(截至到 2013 年 6 月 1 日)。
基本要求:有至少原创 1000 行以上 Lua 语言编程经验,一万行 C/C++ 语言编程经验。有网络服务开发经验:可以独立解决问题(包括但不限于设计合理的通讯协议,评估其效率及安全性)。
有游戏行业从业经验两年以上可以加分。
有兴趣且满足基本要求的同学,可以 email 和我联系获得更详细的信息。
前几天, Lua 5.2.2 发布了, 主要是修复了 4 个 Lua 5.2.1 中已知的 bug . 其中包括前段时间一个同学和我在 email 交流中讨论的一个问题.
我把 Lua 5.2.2 更新到公司项目的主干上,同时需要对我的那本 《》做一些更新,需要把这次的代码更改同步到书里去。这个工作很繁琐,但有它的价值。比如我发现了 Lua 5.2.2 比 5.2.1 的更改远不只官方宣布了 4 处 bugfix ,还有一些小调整,让 Lua 的源码更规整一些。
阿楠同学因为这段时间一直在维护
这个 C# 版的 Lua 项目,我就随便和他通告了一下这次的一些代码变更,方便他同步到 UniLua 项目中去。
讨论之中,他提到 luaD_precall 函数的实现有些诡异之处,没有看明白。我顺着他指出的位置又仔细阅读了一下,果然发现这里存在一个隐藏很深的 Bug 。
我们游戏客户端使用了 Unity3D , 我们不打算给它写 C 插件, 所有的开发都在 mono 中进行的。
由于某些需求,我们需要在客户端解析一些 Lua 脚本(这些脚本同时供我们的服务器使用)。所以,就有了阿楠同学开发的
这个世界上已经有了很多的 .net 版的 Lua 实现,但是都不完整。它们大多是基于 Lua 5.1 甚至更老的版本的。还有一些只能解析 Lua 的字节码(这样很容易实现),而不能让 Lua 源代码直接工作起来。这使得在 Lua 中很常见的 meta 编程变得不可用了。
我上次通读 Lua 的源代码时,Lua 还在
5.1 。当然 Lua 5.0 我也读过,4.0 和 3.2 则读的不多。
最近有一点空闲,想续写我那本 Lua 源码欣赏。按我心里的计划,还有大约 6 章。虚拟机、字节码持久化、C
API 、解释器、GC、库函数。
新添了一章关于虚拟机的,所以重新读了一遍相关源码。发现 Lua 5.2 比上一版修改了不少,几乎每个位置都有修订。
自己读代码和写出来给人看又是不同,真的逐行推敲的话,之前的理解也是经不起琢磨的。为什么要写这一行;为什么这一行在这个位置,而不是在后面;为什么要这么实现,而不是那样实现……
一边写,一边发现对别处的引用会引发新的疑问,继而需要对之前已完成的章节做一些修补。
上一次发布 pdf 时,采用的是日后纸质书的版式。留白太多对于电子阅读其实是很浪费的,读代码尤其不好。所以这次重新排了一下。
这次主要是增加了关于 VM 的新章节。
有兴趣的同学可以下载:《》。但我不建议现在开始阅读,尤其是对不仅仅想随便翻翻的同学。因为我经常修改它,今天看到的版本,可能写完后已经改了不少了。
btw, 在我写完后,发现最近有另一个同学也在写类似的文章。这里给出,有兴趣的同学可以看看。
如何绑定 C/C++ 对象到 Lua 里?通常是创建一个 userdata ,存放 C/C++ 对象指针,然后给 userdata 添加元表,用 index 元方法映射 C/C++ 中的对象方法。
也有另一个手段,直接用 lightuserdata 保存 C/C++ 对象指针放到 Lua 中,在 Lua 中创建一个 table 附加元表来来包装这个指针,效果是类似的。区别在于对象生命期的管理方式有所不同。就这个问题,几年前我 。
绑定 C/C++ 对象到 Lua 里的设计难点往往在这个正确的生命期管理上。因为 C/C++ 没有 GC 系统,依赖手工管理资源;而 Lua 则是利用 GC 做自动回收。这两者的差异容易导致在 Lua 中的对象对应的 C/C++ 对象已经销毁而 Lua 层不自知,或 Lua 层中已无对象之引用,而 C/C++ 层中却未能及时回收资源而造成内存泄露。
理清这个问题,首先你要确定,你打算以 Lua 为主干来维护对象的生命期,还是以 C/C++ 层为主干 Lua 部分只是做一些对这些对象的行为控制。
我个人主张围绕 Lua 来开发,C/C++ 只是写一些性能相关的库供 Lua 调用,即框架层在 Lua 中。这样,C/C++ 层只提供对象的创建和销毁函数,不要用 C 指针做对象的相互引用。Lua 中对象被回收时,销毁对应的 C 对象即可。
但是,也有相当多的项目做不到这点。Lua 是在后期引入的,之前 C/C++ 框架层中已做好了相当之复杂的对象管理。或者构架师不希望把脚本层过多的侵入引擎的设计。
那么,下面给出另一个方案。
我们将包装进 Lua 的 C 对象称为 script object ,那么只需要提供三个函数即可。
因为内存限制问题, 我们暂时放弃了 luajit 。这两天,我想另辟蹊径找到别的方法去加速 lua 程序的运行。
所以我这两天做了这么一个玩具,试一下是否可行。
的东西,它允许你在 Lua 代码中直接写 C 代码。由于是用 tcc 运行时编译运行的,所以你可以获得和 C 一样的效率。(同样,C 语言引入的问题也同样要考虑)
之所以我称之为玩具,是因为它现在还不支持复杂的数据结构。你只能把单层的,以 string 为 key 的 lua table 映射到 C 代码中(表现为一个 user type ,其实是一个 C struct )。目前还不能用数组做数据交互。
它可以利用一个内建类型 object 来持有传递 lua 的对象,但不能操作它。
有兴趣做进一步完善的同学,可以。
昨天我们发现每日构建的服务器突然在一个晚上内存暴增了 8 G ,显然是发生了内存泄露。
之前,我们在 skynet 里留下了许多调试协议,使我们很快的确定了发生泄露的服务:在一张地图的 lua State 中。可以确定是地图的 lua 实现中,有些 lua 对象在不断的生成。生成速度不快,但确实没有人解开引用,导致内存持续增长。
曾经有很多人做过 Lua 的内存分析工具,但是我懒的去搜了,花了半天时间自己写了一个。()
原理是这样的:
我们的系统的应用场合比较特殊,在同一个进程内存在数千个 lua_State 。
Lua 的虚拟机占用的内存已经足够小了,但还是抗不住数量多啊。所以我希望有版本节约一些内存。
最想做的一件事情是把不同 lua_State 中相同的函数字节码合并起来共用一块内存。要做到这一点并不复杂。而且可以提高一些内存访问的效率。(因为大部分 lua 程序在并行执行相同的逻辑)
首先我们需要准备一个用来共享数据块的模块,它必须是线程安全的。因为既然分到了不同的 lua_State 就是想利用并发的优势。针对这个特定需求定制这样一个模块可以做到 lock-free 。
正如 记载的,我们第 2 里程碑按计划在 9 月 30 日完成,但因为赶进度,有许多 bug 。性能方面也有很大问题,大家都认为需要重构许多模块。所以,在最后几天修补 bug 时,许多补丁是临时对付的(因为整个模块都需要重写了)。为此,我们留下了一个月专门重构代码、修改 bug 、并对最后的结果再做一次评测。
这项工作,终于如期完成了。
半个多月前在白板上留下的工作计划还没擦掉。我列出了 12 点需要改进或重写的地方,考虑到内容较多,又去掉了 3 项。在大家的通力合作下,完成的很顺利。
,我们的老系统处理 80 人同一战场混战就让服务器支撑不住了。当时我们的服务器 CPU 达到了 790% 。虽然我们的服务器硬件比较老,配置的是两块 Intel Xeon E5310 @ 1.60GHz ,更新硬件可以有所改善。但这个结果绝对是不能满意的。从那个时候起,我从重写最底层框架开始一步步起着手优化。
昨天的测试结果基本让人满意,在同一台机器上,200 个机器人的混战 CPU 占用率平均仅 130% 左右,而机器人 client 边数据包延迟只有 1 秒,完全可以实用。这离我们的设计目标 ( 500 人同战场流畅战斗)还有一些距离,但考虑到今年新配置两块 Intel Xeon E5-2620 @ 2.00GHz 的话,按其性能指标,应当再有至少一倍的性能提升。
ps. 参考 ,我们计划采购的 [Dual CPU] Intel Xeon E5-2620 @ 2.00GHz Benchmark 16707 分,而目前使用的 [Dual CPU] Intel Xeon E5310 @ 1.60GHz 仅 4160 分。即使仅考虑单线程分数,也在两倍以上。
我们的项目是用 Lua 5.2 标准来写的, 最近想迁移到 LuaJIT 2.0 中。其中碰到的最大障碍是,LuaJIT 2.0 不支持 Lua 5.2 中的 _ENV 特性。而且,看起来将来也不会支持了。
在邮件列表中,LuaJIT 的作者 。
可是我真的需要它,所以只好自己阅读 luajit 的源代码,给它打了个 patch 支持这个特性。
patch (基于 luajit 2.0 的 beta 11) 如下:
最近想试一下, Lua JIT 2.0 能给我们的系统带来多大的提升。但可惜的是,我们一开始就在用 Lua 5.2 来构建系统,而 Lua JIT 2.0 只支持 Lua 5.1 的 API ,在可以看到的时间里,恐怕也不太会去支持 5.2 了。
所以,我只能想办法反向支持 Lua 5.1 。
语法层面最重大的改变是 Lua 5.2 取消了环境表这个概念,转而提供 _ENV 这个语法糖。
许多小细节是 C API 上的变化。这使得按 Lua 5.2 标准写的 C 库,无法在 Lua 5.1 环境下编译。我打算用 Lua 5.1 的 API 来模拟出来。
最近工作展开后, 我们一共有 10 名程序员在目前的项目上工作。我暂时没有和其他人有依赖关系的工作,最近一周在改进以前做的一些东西,在不修改接口的前提下,争取提供更高的性能,以及完成一些之前没完成的功能,为以后的扩展做准备。
最近值得一提的东西是:关于我们的共享储存的数据结构。
最早在设计的时候,是按多进程共享的需求来设计的。希望不同的进程可以利用共享内存来共享一组结构化数据。所以 。这个东西实现的难点在于:一、共享内存不一定在不同进程间有相同的地址,所以不能在结构中用指针保持引用关系;二、不希望有太复杂的锁来保证并发读写的安全性。
后来,我们采用了 Erlang 做底层的框架。在同一台机器上,只有一个系统进程。所以,这个东西可以不必实现的这么复杂。我抽了三天实现,重新实现了一个。这次不考虑跨进程的问题,只在同一进程的不同线程中,让独立的 Lua State 可以访问同一份结构化数据。至于结构化数据支持到怎样的数据类型,我认为和 Lua 原有的 table 类型大致一致就可以了。
最后,就完成了这么一个东西。我认为到目前这个阶段,这个模块还是比较独立的,适合开源分享。以后的工作可能会和我们具体项目的模块整合在一起,还需要做一些修改,就不太适合公开了。有兴趣的同学可以在我的 github 上看到代码。 。
问题是早就提出了的。在
中,就写到一个需求:一个玩家数据的写入者,可以批量修改他的属性。但是,同时可能有其他线程在读这个玩家的数据(通过共享内存)。这可能造成,读方得到了不完整的数据。
我们可以不在乎读方得到某个时间的旧数据,但不可以读到一份不完整的版本。就是说,对玩家数据的修改,需要成组的修改,每组修改必须是原子的。
起先,我想用读写锁来解决这个问题。方案想好了,一直没有实现。只是把读写锁的基本功能实现了。
这几天这个问题被重提出来。因为,前段我们都采用了鸵鸟政策,当问题不存在(事实上我们也没有发现实际中出现可观测到的问题)。
反正探讨了好几个解决方案,一开始都是围绕怎么加锁,锁的粒度有多大来展开的。甚至,我们把其中的一种方案都实现出来了,并写了压力测试程序测试。不过,这些方案都不太令人满意。大家担心锁的开销,以及逻辑代码编写者所需求关心的问题太多,导致有死锁的可能性。
昨天差一点决定用一个地图锁来解决这个问题,就是用牺牲同一个地图进程上,玩家间并行的可能性为代价的。这个方案也不无不可。但昨晚躺在床上一直睡不安稳。因为这样做,就失去了一开始我期望用并行方案来设计游戏服务器的初衷。如果这样,还不如全部退化到单地图单进程来编写程序。那么一定有方法是可以避开锁以及避免让写逻辑的程序员去关心数据共享的读写冲突问题的。
Lua 5.2.1 正式发布有段时间了。虽然相对于 5.2.0 只是一个小版本的提升,但也是有些东西可以拿出来讲讲的。
比如,在这次小版本更新中,字符串类型被分为了长字符串和短字符串两类。长字符串(大于 40 字节的字符串),不再做内部化处理了。
一开始我以为这是为了性能的一处小改进,可以在字符串处理比较多的场合,少做一些 hash 计算和 hash 表插入。后来查了一下邮件列表发现,其实是为了安全性,防止别人做
攻击。一起改变的是字符串的 hash 过程使用了一个随机种子。默认设定和时间有关。值得注意的是,这处改变可能会使得嵌入 lua 的程序每次运行的内存状态不一致,有可能给调试带来一定的麻烦。
我们的提供了一个 C 接口, 在 RPC 调用时, 回调一个事先注册的函数.
C 中标准的回调函数的接口设计, 标准方法是设置一个 C 函数指针加一个 void * 类型的数据指针.
由于我们的游戏逻辑使用 Lua 来实现, 所以这里只需要实现一个 C 函数去调 Lua 机里的函数, 而对应的 void * 自然就是 lua_State * 。
今天,同事在实现服务的热更新功能。发现多次热更新 lua 写的服务会导致一处 core dump ,一直没有找到原因。通过阅读代码,我仔细思考后,确定了 bug 所在。
Lua 5.2 最重大的改进,莫过于 "yieldable pcall and metamethods" 。这需要克服一个难题:如何在 C 函数调用中,正确的 yield 回 resume 调用的位置。
resume 的发起总是通过一次 lua_resume 的调用,在 Lua 5.1 以前,yield 的调用必定结束于一次 lua_yield 调用,而调用它的 C 函数必须立刻返回。中间不能有任何 C 函数执行到中途的状态。这样,Lua VM 才能正常工作。
(C)lua_resume -> Lua functions -> coroutine.yield
-> (C)lua_yield -> (C) return
在这个流程中,无论 Lua functions 有多少层,都被 lua state 中的 lua stack 管理。所以当最后 C return 返回到最初 resume 点 ,都不存在什么问题,可以让下一次 resume 正确继续。也就是说,在 yield 时,lua stack 上可以有没有执行完的 lua 函数,但不可以有没有执行完的 C 函数。
如果我们写了这么一个 C 扩展,在 C function 里回调了传入的一个 Lua 函数。情况就变得不一样了。
(C)lua_resume -> Lua function -> C function
-> (C) lua_call
-> Lua function
-> coroutine.yield -> (C)lua_yield
C 通过 lua_call 调用的 Lua 函数中再调用 coroutine.yield 会导致在 yield 之后,再次 resume 时,不再可能从 lua_call 的下一行继续运行。lua 在遇到这种情况时,会抛出一个异常 "attempt to yield across metamethod/C-call boundary" 。
在 5.2 之前,有人试图解决这个问题,去掉 coroutine 的这些限制。比如
这个项目。它用操作系统的协程来解决这个问题 (例如,在 Windows 上使用
)。即给每个 lua coroutine 真的附在一个 C 协程上,独立一个 C 堆栈。
这样的方案开销较大,且依赖平台特性。到了 Lua 5.2 中,则换了一个更彻底的方案解决这个问题。
在做策划表格解析的时候,我们希望可以在表格里直接填写一些脚本代码。我们的脚本语言使用的 Lua ,所以,直接填写 Lua 代码最为简单。但是,策划同学强烈需要在脚本中直接使用中文。而 Lua 原生并不支持使用中文作为变量名。一开始我们使用了一些变通的方案:比如建立一个字典,把中文词通过程序替换成相应的拼音。倒也能工作。
昨天在午饭途中的电梯里,我想到了另一个方案,用了一个下午实现出来验证可用。
修改 Lua 的语法解析代码,让其支持汉字并非难事。但我不太想通过给 Lua 打补丁,修改 Lua 语言的方式来做这件事情。即,我不想因为这个项目为 Lua 创造一门方言。但是,我们却可以把策划表格中填写的代码当成一种 DSL ,正如之前我实现的 那样。把这部分用 Lua 的方言来实现,把修改的影响减少到最小,而不蔓延到整个系统的实现语言中去,或许是个更好的方法。
因为 Lua 是否支持中文变量名,只是一个语法解析层面的问题。到了虚拟机解析 bytecode 层面就不存在了。即,我们修改 Lua 的实现,让它支持中文变量名,它解析源代码生成的 bytecode ,是完全可以直接在未修改过的 Lua 环境中运行的,甚至连调试信息都完全兼容。
最近几天优化了一下
这是一个大改动,所以写 blog 记录一下。
首先,我为 rmessage 定制了一个 heap alloc ,在使用
rmessage 解包的时候不再调用系统的 malloc 。而是从一个连续内存 heap 上取用内存。这样在删除 rmessage 对象时也会更快。因为只需要把 heap 回收即可。
当然这样会导致 rmessage 解包时用到的内存增加。对于内存紧张,性能关键部分,我还是推荐 pattern 模式。虽然比较难用,但可以保证时间和空间性能。
另外,我增加了
的 Event-based parsing 模式,见新增接口 pbc_decode 。
不过我认为这个 api 不适合直接在 C 里调用,但是用来做动态语言的 binding 不错。现在 lua
binding 中的 decode 就改用这个实现了。这样每次解包就把所有项都解出来,而不用附着一个 userdata 。回避了手动调用 close_decoder 的问题。
btw, 根据一个同学使用的反馈,他们大多不主动调用 close_decoder ,而依赖 gc 回收 decode 过程中产生的 C
对象。但是这些 C 对象申请的内存不会通知 lua ,所以 lua 的 gc 触发条件不会及时触发。这使得 pbc 的 lua binding 可能占用大量内存。我这次的修改主要针对这个问题。
如果你在同一个进程里有多个 lua state , 它们需要共享大量的只读数据, 那么可能就不希望在每个 state 启动的时候都加载和解析一遍这些数据.
所以我们需要一个共享只读数据的方法。
前段时间,我实现了一个
,这个可以保证共享内存的安全读写。不过,如果数据是只读的,那么就不需要这么复杂了。
我们只需要把数据加载到一个 lua state 中,其它的同一进程内的 state 通过 C 接口去读数据就可以了。
今天,我做了,放在了
github 上。
虽然今天发了 twitter ,以及向 lua mailling list 里投递了消息,不过想想还是写一篇 blog 记录一下。
Lua 只支持一种 number ,默认是 double 类型。虽然你可以通过修改 luaconf.h 里的定义,把 lua number 改成 int64 。但是为了 int64 类型而放弃浮点数,恐怕不是大多数人想要的。
int64 通常用在 uuid 上,也就是说不需要对其数学运算,只需要可以比较就好了。我以前最喜欢的做法是用 8 bytes 长的 string 来表示一个 int64 。这样,即可以做唯一的 key 用,又不用做复杂的扩展。
中,对 fixed64 类型,我就是这样处理的。
我们用 lua 做主要的项目开发语言,一直有同学希望可以在 IDE 里单步跟踪调试 lua 代码。我总觉得这个坏习惯是被 Windows 带坏的。当然,很多年前,我也尝试过编写 。后来这玩意半途而废了。因为我觉得没啥实用价值,需要这样去调试 lua 程序的程序员反正也写不好 lua 程序。宁可不要这种工具让 lua 程序员的代码质量能提高一点。
后来过了两年,还在网易时,又有同学要求有一个方便点的调试器。,gdb like 的界面,用 C/S 方式调试,并用 GTK 配了一个 GUI 的 client 。主要就是远程设置断点,观察变量等。有兴趣的同学可以。
这套东西不多提了。今天又有人老话重提。我觉得吧,与其做一个交互式的调试器,不如做一个 trace log
简单实用。毕竟在生产环境,不是有那么多机会让你中断下服务单步调试的。
今天花了将近 3 个小时帮同事看一个崩在 lua VM 中的 bug 结果打乱了进度,没有在年前把预想的东西做完。其实说起来这不是个大问题,以前也碰到过。我检讨自己没有在看到出错时的调用栈时去看一眼 lua 相关的代码。如果是那样,因为以前遇到过同样的问题,所以就可以条件反射出问题原因,而不用荒废宝贵了数小时时间了。
唉,这下整合的进度没接上,过年不能自己一个人接着做下面的活了。
下面记录一下这个 bug ,提醒自己第三次遇到时不用再花时间找问题:
lua 5.2 正式发布了,对于 lua 语言本身的修改,重中之重就是对 environment 这个概念的修改。
可以说, 5.1 以前的 environment 已经没有了。environment 对于制造一个安全的沙盒(或是实现 DSL)是一个很重要的语言特性,我以前很喜欢使用,但也很容易用错。这次的修改我认为是一个谨慎的决定,并使得 lua 语言更为精简和严谨了。
我这样理解 5.2 中的 environment 。本质上,lua 取消了原有意义上的 environment 。所以我们可以看到 C Function 不再有环境了。function 、在 lua 中称为 closure ,仅仅只是函数体和 upvalue 的联合体。这简化了 lua 语言本身。全局变量实际上只是一个语法糖,编译时再前面加上了 _ENV. 的前缀。这样,从 load 开始,第一个 chunk 就被加上了 _ENV 这个 upvalue ,然后依次传递下去。
这个设计基本可以取代以前使用 getfenv/setfenv 改变函数环境的方法。但是又不完全等价。总体来说,增加了一些限制,但不太容易写出 bug 的代码了。
比如说,现在想给返回一个独立环境的函数,可以这样写:
前几天写的
初衷就是想可以方便的 binding 到动态语言中去用的。所以今天花了整整一天自己写了个简单的 lua binding 库,就是很自然的工作了。
写完了之后,我很好奇性能怎样,就写了一个非常简单的测试程序测了一下。当然这个测试不说明很多问题,因为测试用的数据实在是太简单了,等明天有空再弄个复杂点的来跑一下吧。我很奇怪,为什么 google 官方的 C++ 版性能这么差。
我的 lua 测试代码大约是这样的:
local protobuf = require "protobuf"
addr = io.open("../../build/addressbook.pb","rb")
buffer = addr:read "*a"
addr:close()
protobuf.register(buffer)
for i=1,1000000 do
local person = {
name = "Alice",
local buffer = protobuf.encode("tutorial.Person", person)
local t = protobuf.decode("tutorial.Person", buffer)
100 万次的编码和解码在我目前的机器上,耗时 3.8s 。
几个月以前,在我在 blog 上曾谈及 Lua 5.2 的改进。 。
周末休息,我把这桩事挖出来娱乐一下,花了一整个晚上做了实现。把 lua 的每个线程锁定在独立的 lua state 中,强迫线程之间通过消息管道的方式通讯。经过测试,Lua 5.2 每个独立的 state 占用的内存很小。通过自定义 alloc 函数可以测算出,一个干净的 32bit state ,不含任何库函数时,占用内存量在 2K 以下(1726 bytes)。如果加载基本库,也仅仅占用不到 4K (3265 bytes)。若把所有 lua 官方标准库加载进来,才会上升到 10K 以上(12456 bytes)。
对于 luajit 2 ,这个基础开销会大一些,最小开销也在 10K 左右 (8058 bytes) 。加上 ffi 达到 30k (31605 bytes)。不过 ffi 可以使 lua 代码直接使用 C 的数据结构,在实际运用中还可以减少内存的使用。
废话不多说, ,有兴趣的同学可自取。
这个娱乐项目命名为 Ameba ,暗示每个代码单位都足够的小,功能简单。它们必须通过很少的 send/recv 和外界通讯。目前,通讯的数据类型仅限于 number boolean 和 string 。
。漫长的流程到今天已经快两年过去,终于等到了 beta 版。我十分期待它可以在 2011 年内正式发布。在这几经折腾的两年里,许多新特性企图挤进 5.2 版,又最终被否决。
当我们审视,似乎看不到太多耳目一新的东西。但如果仔细阅读一下源代码,就会发现,大部分地方都重新实现过了,以配合这些表面上看起来不大的修改。如果你对 Lua 有足够理解,会发现,这次最激动人心的改进是 "yieldable pcall and metamethods" 。官方也把之列为 Main changes 第一条。语言上的重大新特性 goto 却被列在末尾。
当然,这只是我粗浅的理解而已。没有经过实践使用 5.2 一段时间,下这样的论断有点太草率。不过我还是想谈谈,这点改进可以给我们的开发带来什么。
coroutine 的 yield 现在几乎可以在任何地方使用了。我用了几乎,是因为它依然有一些限制。这些限制不大容易说的很清楚,为了理解其限制,我花了一整天实现阅读 lua 5.2 beta 版的源代码。这个话题下次有机会我再另写一篇 blog 总结一下。今天只谈应用。
最近 Lua 社区非常活跃。6 月 22 日发布了 Lua 5.2.0 (beta-rc2) 。今天(6 月 24 日) 发布了 LuaJIT-2.0.0-beta8 。
虽然 luajit 和 lua 5.2 还有点小矛盾,luajit 没有完全支持 lua 5.2 的迹象。不过,这些对 Lua 社区都是好消息啦。可能对于 lua 用户会有点小纠结,到底是追随官方的 5.2 版呢,还是去用性能更好的 luajit2 。我比较在意性能,暂时先投靠 luajit 了。反正和 5.2 区别也不大。更重要的是,luajit2 提供的 ffi 库相当之好用,极大的减少了我们写 C 库的 lua binding 的负担。从某种角度可以看到另一个问题,为基础设施模块设计出良好的 C 接口(而不是 C++ 的)是多么的重要。
zeromq 是用 C++ 实现的,但它提供的是简洁纯粹的 C 接口。这让它相当利于 binding 到其它语言中使用。之前,已经有了成熟的
可供使用。它分别实现了 ffi 和不带 ffi 的版本。不过也正因为此,封装层包裹的很淡疼。如果只支持 ffi 版本的话,其实这个工作可以做的非常简洁。
出于实践 luajit ffi 库的目的,也为了让这部分代码看起来清爽一点。我花了半个下午自己封装了一下 zeromq 。所用时间比在 windows 下配置安装那个现成的 lua-zmq 所用时间看起来更少(不需要装 msys ,cmake 等等淡疼的玩意)。谁再下面留言说不要重复造轮子了,我也不打算跟它急了。吵架的时间都比写代码时间长。我们从来不会把写一遍 hello world 看成重新制造轮子不是么?使用 ffi 去 binding C 库实在是太容易了,不比写 hello world 更复杂。
前几天分析了 lua gc 的实现细节。这里先汇总一下:
btw, 阅读 lua 的代码是段很有趣的经历。但如果是重头读 lua 的源码,建议从简单的部分读起。gc 恰巧是最难的一段。 的作者 Mike 在这方面很有发言权,他在回答 Which OSS codebases out there are so well designed that you would consider them 'must reads'? 这个问题时,列过一张推荐。我的观点一致,Lua 是少有的设计优秀,C 程序员必读的代码。
从小处说,如果想进一步改进,那是必须仔细研读的(但这绝对不是主要原因)。Lua 的 GC 实现的已经相当不错了,想找出实现中的问题,改进算法,可能很难。如果有多核处理器,那么把 GC 放到一个独立线程里去做倒是可以考虑的。
如果没有前面的研读,恐怕只能用一把大锁来安全处理多线程的 GC 了。lua 的代码为多线程安全预留了 lua_lock 和 lua_unlock 两个 api 。默认是用宏定义出来关闭的,必要的时候可以改写它们。所有的对外 api 都加入了 lock 的调用。
但是,用它来实现多线程的 gc 是完全没有意义的。GC 部分永远不能并行处理。这个东西只是为了多线程访问同一个 lua state 提供了安全保障而已。
下面我们看看能做点什么。
今天来说说 write barrier 。
在 GC 的扫描过程中,由于分步执行,难免会出现少描了一半时,那些已经被置黑的对象又被修改,需要重新标记的情况。这就需要在改写对象时,建立 write barrier 。在扫描过程中触发 write barrier 的操作影响的对象被正确染色,或是把需要再染色的对象记录下来,留到 mark 的最后阶段 atomic 完成。
和 barrier 相关的 API 有四个,定义在 lgc.h 86 行:
今天来看一下 mark 过程是怎样实现的。
所有的 GC 流程,都从 singlestep 函数开始。singlestep 就是一个最简单的状态机。GC 状态简单的从一个状态切换到下一个状态,循环不止。状态标识放在 global state 的 gcstate 域中。这一点前面谈过。
开始的两个状态和 mark 过程有关。
初始的 GCSpause 状态下,执行 markroot 函数。我们来看一下

我要回帖

更多关于 多核处理器工作原理 的文章

 

随机推荐