说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

如何让Ruby项目速度提升10倍

HTML文档下载 WORD文档下载 PDF文档下载
如何把一个Ruby项目的运行时间从20秒优化到1.5秒?值得开发者注意的是,在Ruby中调用方法很影响速度,所以作者对代码进行了模块化处理和重复使用。

作者详细描述了他是如何把一个Ruby项目的运行时间从20秒优化到1.5秒。值得开发者注意的是,在Ruby中调用方法很影响速度,所以作者对代码进行了模块化处理和重复使用。下面是笔者对原文的翻译:

这篇文章主要介绍了我是如何把ruby gem contracts.ruby速度提升10倍的。

contracts.ruby在我项目里用来添加代码合约(code contracts)到Ruby中。看起来差不多是这样的:

Contract Num, Num => Numdef add(a, b)  a + bend
只要add方法被调用,参数和返回值都会被检查。

20秒

本周末,我对该库进行了测试,发现其性能非常糟:

                                   user     system      total        realtesting add                      0.510000   0.000000   0.510000 (  0.509791)testing contracts add           20.630000   0.040000  20.670000 ( 20.726758)
这是在随机输入下,运行1000次以后的结果。

所以,当给一个函数加入合约功能后,运行速度明显下降(约40倍这样),对此,我进行了深入的研究。

8秒

我取得了较大的进展,当传递合约时,我调用success_callback函数,该函数是个空函数,下面是这个函数的整个定义:

def self.success_callback(data)end  

原来函数调用在Ruby中是非常昂贵的,仅删除这个调用,就节省了8秒钟:

                                  user     system      total        realtesting add                     0.520000   0.000000   0.520000 (  0.517302)testing contracts add           12.120000   0.010000  12.130000 ( 12.140564)
删除其它一些附件函数的调用,时间花费开始从9.84-> 9.59-> 8.01秒,该库的速度马上提升到以前的两倍了。

现在,事情变的有点复杂了。

5.93秒

这里有许多年种定义一个合约的方式:匿名(lambdas)、类 (classes)、简单旧数据(plain ol’ values)等。 我有个很长的case语句,用来检测合约的类型。在此合约类型基础之上,我可以做不同的事情。通过把它改为if语句,我节约了一些时间,但每次调用这个函数时,我仍然耗费了不必要的时间在仔细检查这个判定树上面:

if contract.is_a?(Class)  # check argelsif contract.is_a?(Hash)  # check arg...

当定义合约和构建lambda时,对树只做一次检查:

if contract.is_a?(Class)  lambda { |arg| # check arg }elsif contract.is_a?(Hash)  lambda { |arg| # check arg }

然后,我将完全绕过逻辑分支,通过将参数传递给预计算的lambda来进行验证,这样就节约了1.2秒时间。

                                  user     system      total        realtesting add                      0.510000   0.000000   0.510000 (  0.516848)testing contracts add            6.780000   0.000000   6.780000 (  6.785446)
预计算一些其它的If语句,差不多又节省了1秒时间:

                                   user     system      total        realtesting add                      0.510000   0.000000   0.510000 (  0.516527)testing contracts add            5.930000   0.000000   5.930000 (  5.933225)
5.09秒

将.zip转换为.times又为我节省了1秒时间:

                                   user     system      total        realtesting add                      0.510000   0.000000   0.510000 (  0.507554)testing contracts add            5.090000   0.010000   5.100000 (  5.099530)
结果证明:

args.zip(contracts).each do |arg, contract|
上面的代码要比下面这个慢:

args.each_with_index do |arg, i|
要比下面这个更慢:

args.size.times do |i|
.zip要花费不必要的时间复制和创建新的数组。而我认为,.each_with_index之所以慢,是因为它受制于背后的.each,所以它涉及到两个限制而不是一个。

4.23秒

下面再看些细节的东西,contracts库在工作时,它会为每一个方法添加class_eval(class_eval要比define_method快)的新方法,这个新方法里有一个对老方法的引用,当调用新方法时,它会检查参数,然后根据参数调用老方法,然后再检查返回值,并且返回值。所有这些都会调用Contract class的check_args和check_result两个方法。我取消了这两个方法的调用,并且对新方法进行正确检查,结果又节省了0.9秒:

                                   user     system        total        realtesting                          0.530000   0.000000   0.530000 (0.523503)testing contracts add            4.230000   0.000000   4.230000 (  4.244071)

2.94秒

在上面,我已经解释了如何基于Contract类型创建lambda,然后使用这些来检验参数。现在,我换了种方法,用生成代码来替代,当我使用class_eval创建新方法时,它就会从eval中获得结果。一个可怕的漏洞,但它避免了一大堆方法调用,并且节省了1.25秒:

                            user    system     total   real    testing add            0.520000 0.000000 0.520000 ( 0.519425)    testing contracts add  2.940000 0.000000 2.940000 ( 2.942372)

1.57秒

最后,我改变了调用重写方法的方式,我先前是使用引用:

# simplificationold_method = method(name)= method(name)class_eval %{%{    def #{name}(*args)def #{name}(*args)        old_method.bind(self).call(*args).bind(self).call(*args)    endend}}
我进行了修改,并使用alias_method方法:

alias_method :"original_#{name}", name:"original_#{name}", nameclass_eval %{%{    def #{name}(*args)def #{name}(*args)        self.send(:"original_#{name}", *args)self.send(:"original_#{name}", *args)      endend}}

惊喜,又节省了1.4秒。我不知道为什么aliaa_method会如此地快,我猜是因为它跳过了一个方法的调用和绑定到.bindbind。

                                 user     system      total        realtesting add                      0.520000   0.000000   0.520000 (  0.518431)testing contracts add            1.570000   0.000000   1.570000 (  1.568863)

结果

我们成功的将时间从20秒优化到1.5秒,我不认为还有比这更好的结果的了。我所编写的 这个测试脚本表明,一个被封装过的add方法要比常规的add方法慢3倍,所以这些数字已经足够好了。

想要验证上面的结论很简单,大量的时间花在调用方法上是只慢3倍的原因,这里有个更现实的例子:一个函数读一个文件100000次:

                                  user     system      total        realtesting read                     1.200000   1.330000   2.530000 (  2.521314)testing contracts read           1.530000   1.370000   2.900000 (  2.903721)

稍微慢了点!add函数是个例外,我决定不再使用alias_method方法,因为它污染了命名空间,并且这些别名函数会到处出现(文档、IDE的自动完成等)。

其它原因:

  1. 在Ruby中调用方法很慢,我喜欢将代码模块化和重复使用,但或许是时候将更多的代码进行内联了。
  2. 测试你的代码!删掉一个简单的未使用的方法时间从20秒缩短到了12秒。

其它尝试

1.方法选择器

Ruby 2.0里缺少方法选择器这一特性,否则你还可以这样写:

class Foo Foo  def bar:beforedef bar:before    # will always run before bar, when bar is called# will always run before bar, when bar is called  endend  def bar:afterdef bar:after    # will always run after bar, when bar is called# will always run after bar, when bar is called    # may or may not be able to access and/or change bar's return value# may or may not be able to access and/or change bar's return value  endendendend
这样可能会更加容易编写decorator,并且运行速度也会加快。

2.关键字old

Ruby 2.0里缺乏的另一特性是引用重写方法:

class Foo Foo  def bardef bar    'Hello''Hello'  endendend end class Fooclass Foo  def bardef bar    old + ' World'+ ' World'  endendendendFoo.new.bar # => 'Hello World'Foo.new.bar # => 'Hello World'
3.使用redef重新定义方法:

Matz曾说过:

为了消除alias_method_chain,我们引入了Module#prepend,prepend前面加#号,这样就没机会在语言里加入冗余特性。

所以如果redef是冗余特征,也许prepend可以用来写decorator?

4.其它实现

目前为止,这些都已经在YARV做过测试。

来自:adit.io

浅谈Hive vs. HBase 十款最新的JavaScript开发工具 JavaScript Promise启示录 百度携多位安全专家深入探讨安全人才快速培养机制 卡饭技术代表严佳:引入安全人才,企业应遵循S.L.E.E.P、S.T.A.R法则 百度资深研发工程师周晓波:平台+数据 百度培养安全人才的重要思路 代码审查是如何抹杀开发者积极性的? 事件分析平台EventHub开源 云计算智能交通行业论坛:李德毅院士谈智能车 多家知名企业分享应用实践 我伙呆!CRM生死对手微软及Salesforce或将联手 阿里巴巴曾鸣:数据时代来临 OpenStack企业应用之路技术培训:虚机迁移、HA、VDI、跨Hypervisor管理 云计算技术背后的那些天才程序员:KVM之父Avi Kivity 中国气象局联手阿里云:海量气象数据变“活数据” 浪潮首度公布“I2I计划” 全面接盘IBM X业务 一周消息树:Surface Pro 3想替代笔记本太难,微软正逐渐迷失方向 就是要赚钱!NativeX推自定义原生广告交易 继智能手机后的下一个战场:智能家居 鏖战云端——亚马逊的生存法则 如何保住公司的人才:创造良好的学习氛围 主宰全球的10大算法 专访Google数据科学家彭晨:大数据成为潮流走近各行各业! 强横Metal技术来袭,能否取代OpenGL? Swift横空出世,Xcode 6 Beta有了哪些新变化? 《近匠》GameMei:简单易用、快速开发商用游戏 Gartner:2014年一季度浪潮服务器出货量中国第一 【技术博客推荐】张安站:大数据时代下的商业存储 Amazon继续登顶IaaS魔力象限,榜眼是微软而非Google 友盟Q1移动互联网报告:解读三线城市移动互联网用户 智能生活 “视”不可挡——首届TCL杯HTML5智能电视开发大赛等你来挑战 Mobile First!开源HTML5游戏引擎Kiwi.js 1.0正式发布 急,NOTES与SQL SERVER的问题 asp实例 我的Access库文件原来大小是6.78M,我清空所有记录和减少很多记录时,为什么大小没改变? 关于delphi动态加载控件,在线 代人提问:请问.dcu文件是干什么的?如何才能生成该类型文件。 怎么响应Enabled属性 asp.net做网页,运行后,<asp;....></asp:...>字段仍然存在?? 请教VB高手们一个高难问题! 请问,存储过程在VC如何调用,用ADO,ODBC。 怎么实现这个SQL? 没事瞎问~~~黑客软件能走向海外吗?(比如扫描器,键盘记录器,木马等) windows protect --error ,我装不了系统啊 DbEdit里的数据用adoquery如何更新才好? 有没有办法调试存储过程? 我是个菜鸟,请教大家,VC++和C++ Builer的优点和缺点。 请问如何改变显示文本的颜色? delphi 6 的补丁包 浏览器为NS6时如何获取屏幕分辨率? 如何控制 ValueListEditor ?有一个Help这样写道: 各位斑竹请进来,紧急求救呀!!!!! 是否可以把用asa建的数据库转变为access? 在java中如何压缩和解压文件夹? 如何在运行期把最大化,最小化按钮灰掉!? 求一句广告词 请教一个毕业设计的问题,关于在c++buileder中OLe对象调用:怎么样把word中的表格导入到数据库 请问如果在首页中加入一个论坛的登陆对话框? 这样的IIS错误信息,是什么意思,怎么解决? 问一下,vb怎么连接数据库呀`~(access)有代码吧~~给分呀` 如何画箭头? C#进行嵌入式开发,方便吗? 她为什么看上我??! 如何用BCB从mp3文件中得到歌名,歌手名,唱片集,流派等! 各位知道有关于ORACLE系统管理实例方面的书籍吗? ADO访问access数据库,怎样编辑一条记录啊? 请教各位关于win2000默认共享的问题 喜欢德国队的进来,坚韧不拔的意志和不到最后一刻不放弃的精神!!!! 如何判断图像大小 com的参数传递问题 请问:如何将pb数据库中的表倒入vf数据库中???急!!!!!! 大家好,关于数据库的问题请,大家帮忙。 一个关于button的简单问题 有没有将图片设为桌面的api函数,若有怎样用?若没有,怎样实现该功能? 我在adotable中删除数据它老是报错:”键列信息在最后读取中改变“(急用) 如何将CString类型转换为int型? wyx病毒的所有症状表现、危害性、解决方法。 在线等待--100分(很简单的问题) 急急急!! 隐藏任务栏的问题 急急急 我配置的cgi为什么不能访问? 实在不知哪里有问题,csdn....,帮忙up一下好吗。50分捧送。 用热的快在电壶里烧水对电壶有害没?听说那样烧久了壶就不保温了 某同学在做电解水的实验时通电一段时间后,水的质量减少了2.7g(不考虑蒸发),理论上的H2和O2质量分别是多少 在金属活动顺序中H之后的金属都不和酸反应,那么硫酸铜、硝酸银怎么制作出来的?什么叫强氧化性的酸? 电壶烧水,一段时间后发现壶盖在跳动,这种现象中能是整样转化的? 苯酚钠溶液和通入二氧化碳成什么? 下列物质可以由相应的金属和稀酸发生置换反应而制得的是A cuso4 B alcl3(说明理下列物质可以由相应的金属和稀酸发生置换反应而制得的是A cuso4 B alcl3(说明理由,急…给好评呦) 电水壶不烧水时壶身与电座分开放好吗 一根铜导线和一根镍铬合金线.长短粗细都相同.把它们并联在电路里,通过哪根导线的电流大?为什么? 下列物质中,能直接用金属与稀酸反应制取的是A.Agcl B.Feso4C.Fecl3 D .Cuso4哪个正确呢,思路是怎样的,能告诉吗 电壶烧水把电能转化为()能1.2.热能 3.机械能 NaH是一种离子化合物,其中钠显+1价.NaH与水反应放出氢气.下列叙述错误的是A.NaH还原剂,水是氧化剂B.生成的另一种产物是氢氧化钠C.NaH的氧化产物是氢气D.水的氧化产物是氢气请问答案C正确吗, 如果利用金属与酸来制取FeCl3或CuSo4是否可以?为什么? 所有的原电池中是不是都是电解质溶液中的阴离子移向负极,阳离子移向正极? 从化学角度分析,甲醇有哪些特性?有什么通途? 马上就要!硫酸钾与氯化钡反应方程式 春雨的诗句15句 甲醇、丙醇是怎么命名的,就是说有什么特征,为什么这么叫还有甲酸,乙酸还有那个丙三醇, 求 硫酸钾与硝酸钡反应的方程式 电解水反应的化学方程式为 . 氢化锂是制作氢气的来源,反应化学式为LiH+H2O=LiOH+H2↑.计算80g氢化锂与足量水反应产生氢气质量 盐酸与硫酸钾反应方程式互滴法鉴别的......用互滴法鉴别是硫酸钾和盐酸 从电解水的化学方程式 金属氢化物可以快速与水反应制取氢气,如:氢化锂与水反应的化学方程式为LiH+H2O=LiOH+H2.若制取10克氢气,需要消耗多少克氢化锂? 细菌吸收紫外线最快的波长是 电解水的化学方程式 化学上利用氢化锂和水在常温下反应制取氢气同时生成氢氧化锂反应的化学式为LiH+H2O=LiOH+H2↑生成的氢氧化锂能和多少克质量分数为10%的稀盐酸恰好完全反应 水在直流电作用下分解成氢气和氧气属于化学性质还是化学变化 电解水的反应的化学方程式 苯酚,NaOH,CO2怎么反应?生成物好像是水杨酸钠反应式是什么?还有反应条件~ 水在直流电的作用下分解成氢气和氧气,此过程是将——能转化为——能. 采用紫外分光光度计法测定时如何选择分析波长 苯酚与过量的烧碱反应后通入二氧化碳,未出现沉淀的原因可能有哪些 b.在常温下,水可以变成水蒸气 c.水在直流电作用下能分解成氢气和氧气 哪个是化b.在常温下,水可以变成水蒸气c.水在直流电作用下能分解成氢气和氧气哪个是化学性质 ( )C2H6OH+( )O2---点燃---( )CO2+( )H2O 电解铜现在价格? 硼砂、硼酸属于什么行业 一句写春雨的诗句 电解铜价格多少一吨 硫酸铜溶液中提取硫酸铜粉末? 原电池里的盐桥里面的离子能移动到溶液中吗?同时溶液中的离子能通过盐桥吗?如果只是让离子集中在盐桥与溶液接触的部分使两边溶液保持电中性,那么突然拿掉盐桥后,盐桥里面的离子不就 电解铜现在什么价格 向苯酚里面滴加溴水会有沉淀生成吗? 电解质溶液在原电池中有什么用?书上说“原电池一定要形成闭合回路”,我不明白“闭合回路”是什么意思?参考书里说要离子的移动来导电,为什么?电子从负极流向正极,然后在正极和水中的 谁知道2004年7月电解铜价格?准确点的! 酸度和PH值有什么区别?表征实验中什么时候适宜测量酸度,什么时候该测量PH?还是二者皆可呢? 电解水的化学方程式有几个? 物体重30N,用F=50N的力垂直压在墙上静止不动,物体所受的摩擦力是=?N 哪些蛋白质可用紫外吸收法测浓度 电解水化学方程式表示的意义? 亚硫酸钠的水溶液和二氧化硫生成什么 蛋白质中存在着哪三种氨基酸具有紫外吸收特性? 电解水的化学反应方程式 环保甲醇燃料对身体有害吗 Phe、Tyr、Try(p)对紫外吸收峰在多少nm?蛋白质的最大吸收波长为多少nm? 有哪些根据化学结构计算最大紫外吸收波长的经验规则 我们怎样保护环境的理由 298K ,1大气压下.气态水的标准摩尔生产焓减去液态水的差值大约是44KJ/MOL,而水的气化热是40KJ/MOL .求之间的关系! 紫外最大吸收波长是650nm的话,荧光激发波长该选什么?如果激发波长选了650nm,那根据斯托克斯定理,发射波长不是在可见光外面了? A物体重30N.用F50的力垂直压在墙上静止不动则A受到多少摩擦力.物体B重30N.受20N力.水平推力静止不动.物体B受多少摩擦力. 298K,OsO4(S)标准摩尔生成焓为-391,OsO4(s)的标准摩尔升华焓为56,反应OsO4(g)=Os(s)+2O2(g)标准摩尔焓变?这是热化学反应里的一道题,看来下面多数是来混分数的.
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘