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

如何让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

云落地@北京,北京市北斗公共平台或开放API? GitHub再遭攻击 主要服务中断约一小时 移动开发者将有望拥有.app域名使用权 微信将推“游戏中心”?移动互联网入口圈地加剧 美国航空公司首次开放API 并举办“黑客马拉松”编程大赛 谷歌数据中心设计的10条“黄金法则” GitHub时代:为什么我们都在开源 VMware发布Horizon Suite 三款应用打包折扣低 TOP30+应用排行榜:二月份国内外综合榜单 开源是否意味着不需要负任何责任? 微软年度科技展 酷炫技术知多少 图解Hadoop生态系统 HTML5并不给力 微软IE10又默认启用Flash Oracle出新提案:Java正逼近iOS? TIOBE 2013年3月编程语言排行榜:厚积薄发 Ruby反超Perl 清华大学开源镜像站将被关闭 发表公开信 前Google资深研究员赵勇回国创业 专注计算机视觉/模式识别 开发者福音,微软开源Kinect源代码 日本很积极:富士通和NEC确认将推Tizen设备 10大支持移动“触摸操作”的JavaScript框架推荐 数据科学家的争夺及美分析学专业研究生院的建立 从开放走向封闭 Netflix宣布停止发布API key 《植物大战僵尸》神秘续作:可能是一款横版游戏 高性价比:GlassUp宣布支持Windows Phone系统 未挖掘到足够信息:非收集数据少,是利用率只有1% 下一个帝国:谷歌收购初创公司DNNresearch 苹果更新Apple Store应用,可查看零售店库存 开源软硬件:福特汽车的OpenXC计划 Temple Run:Oz稳坐付费榜头把交椅,迪士尼名利双收 Facebook开放图谱示好开发者 新增书籍电影等九项操作 源代码管理的六大视觉模式 JSP+JDBC环境中如何配置jConnect连接Sybase数据库?需要安装jdk吗? 急,高分求解Font问题 我编写了一个读写COOKIE的公用函数在我的工程中使用,老不通过,麻烦大哥们帮我诊断诊断 请问怎样获取托盘窗体的句柄??(给一百分) 请帮帮忙,如何调用dtswiz.dll 如何用BDE Administrator设置数据库别名(不用ODBC)访问Oracle数据库。高分相送! 对不起,我知道这个问题很简单,但是我一直没有搞出来,您能告诉我吗?datagrid中进行insert,delete,update操作? 寻求eVC用ADO操作cbd数据库的代码。 两个关于枚举类的问题 问用过struts的朋友一个问题,给100分! huffan交流系数表在C中如何表示?????? 线程问题~~~ 有哪位知道在 Windows 2000 下 锁定计算机时应用程序可以得到什么消息啊? 急!!关于数据的导入导出!! Cells不被VB支持吗? 获取字段类型 如何将DataGrid1中的数据打印出来, 如何使程序自动修改CEdit中的文字? 请问在.NET类库中有这样的类吗? 如何知道某个表格被锁 求助capboy等网络视频播放高手!!! 菜鸟问题:在VC中用ALTER TABLE指令需要什么头文件? 请高手帮忙,感觉难度很高 大家做GUI时有什么好用又免费的日期控件呢? 在javabean中用sql server2000的jdbc驱动连不起数据库! 添加图片到数据库中,奇怪现象! 如果我已经得到了一个DOM树, 我第一次运行HTML HELP时,new怎么会是无效的? 我的 程序在调用FORM.ACTION的用法不正确。能告诉我怎样用它 怎幺样实现一对主从表的数据库基本操作?用ADOQuery连接SQL2000. 有关统计打印的问题 一个关于用VB调用C++builder编写的DLL文件,DLL的API函数中存在函数指针。请各位高手给点意见! 大专生如何考研?(up有分) ★★ 为高兴而送分 ★★ 关于打印的问题,请各位帮忙 请问应该用何种信用卡 利用INTERNET远程连接问题 移动办公相关问题?如在两台机器里编写同一段Web程序。 怎样将oracle sql中的in语句改为exists语句? 求教win 2000双网卡上宽带的问题,请高手帮忙! win2000,给文件改日期的命令是什莫??? 为什么我的背景图片显示不出来? 请问如何在DAO的SELECT语句中使用COUNT等运算符? 请教:请问在程序中怎么在Check box 前边的小框上打上对号 老板让我一个月内学会vb,大家说可能吗? 我装了project 2000后,原来的office 2000只能用50次了,怎么回事? 请能提供DES和RSA算法的源程序呀,vb或VC的都行,最好是VB 请问如何让一个报表的内容同一个DBGrid一致,可以自由设置字段的是否显示? 我用vb新建了一个文件请问如何能双击就能打开我的程序,就跟word一样例:我新建一个kk.doc 只要双击kk.doc 就能打开word 使用Enumwindows的问题 我的jsp页面能够运行但是WEBLOGIC显示了<2002-9-2 上午10时41分13秒> <Error> <HTTP> <Connection failure 请问木头表面被浓硫酸腐蚀之后,是不是在潮湿的空气中会更脆弱(被腐蚀的更快)? 一块圆形玻璃多少钱,含加工磨边费用! 玻璃磨边机 磨边标准玻璃磨边机 磨出的玻璃边 什么样的是OK的.都有什么类型的不良状况(做简单现象描述),不良原因是什么.请专家作回答,谢谢一楼的回答.我补充下我想知道,都有什么磨 什么是测量准确度? 地质灾害评估费是多少? 细胞外液通过什么与外界进行物质交换? 这个是什么修正液 长江三角洲在什么地方 细胞可以直接与什么进行物质交换 过酸过碱,温度过高都是对蛋白质是影响空间结构使其失活吗?外界对于蛋白质的影响除了影响空间结构使其失活,还有其他方式吗?是什么造成的? C语言s=1/n+1/(n+1)+1/(n+2)+…+1/m之和.其中:n 玻璃磨边的要求,规范之类?玻璃家具的磨边要求, 蛋白质空间结构改变会不会影响其生物活性 人类男性正常精子中的常染色体和性染色体的组成是A.44+XY B.22+Y C.22+X或22+Y D.22+X 细胞外液是细胞与细胞进行物质交换的媒介这句话对吗 日本地震其他国家的汽车会涨价吗 /{(m-n)!}c语言怎么编程 如图所示,条形磁铁竖直放置,一水平圆环从磁铁上方位置Ⅰ向下运动,到达磁铁上端位置Ⅱ,套在磁铁上到达中部Ⅲ,再到磁铁下端位置Ⅳ,再到下方Ⅴ.磁铁从Ⅰ→Ⅱ→Ⅲ→Ⅳ→Ⅴ过程中,穿过圆 蛋白质形状改变就是空间结构改变吗,比如协助扩散载体蛋白改变形状叫空间结构改变,还有酶催蛋白质形状改变就是空间结构改变吗,比如协助扩散载体蛋白改变形状叫空间结构改变,还有酶催 人类男性正常精子中的常染色体和性染色体的组成是求大神帮助A.44+XY B.22+Y C.22+X或22+Y D.22+X 一圆形闭合金属线圈从条形磁铁的N极平行于轴线向条形磁铁的中间快速移动时,引起线圈内磁通量变化的有两个方向的磁场:磁铁内部的磁场和外部的磁场.为什么?不管怎么移,内部磁场的磁通 日本地震后,本田,丰田停产了,那车子会不会随着涨价? C语言:执行后,m和n的值是多少?int m=1,n=0,s;int a=1,b=2,c=3,d=4;s=(m=a>b)&&(n=c>!d);0,0的答案是怎么出来的?我不能理解~还有,那m和n的初值难道没有意义么?我是自学者,所以比较菜,希望回答详细一点, 中国古代有哪些象形文字? 日本地震 单反会涨价吗? 玻璃茶几边缘为什么磨得圆滑, 中国古代象形字有哪些? 如何做因果分析法鱼刺图及如何应用 沈阳1日游有什么推荐的?要自然风光的!市内可以不用考虑了. 中国象形文字 目怎么写 鱼刺图制作我用WORD里的绘图箭头做出图形来,但是怎样在箭头处添加文字呀? 智利有哪些值得观赏的自然景观和著名景点?想去智利旅游~ 中国象形字大全花的象形字是什么 请问大家一下化学题目 帮我一下 谢谢就告诉我吧打心底谢谢大家了3g 地质灾害易发性与危险性区别 重铬酸根在酸性环境中与亚硝酸根的反应方程式 中国汉字做姓氏读什么?1, 句:作姓氏时读 2, 黑:作姓氏时读 3, 区:作姓氏时读 4, 朴:作姓氏时读 5, 仇:作姓氏时读 6, 查:作姓氏时读 7, 繁:作姓氏时读 8, 员:作姓氏时读 9, 蕃:作姓氏 长江三角洲是我国地面沉降严重的地区之一,下图示意1986—2008年长江三角洲某地区地面沉降的发展变化,读图完成4—5题.5.造成长江三角洲地区地面沉降的主要原因是A.地震 B 中国的文字是由象形文字开始,那英语呢? 谁知道一道物理题了解的告诉下哟,打心底谢谢大伙了3g 铁化合物的颜色 二氧化硫和亚硝酸盐在酸性条件下能反应么 长江三角洲经常发生的自然灾害是 地质灾害调查跟地质灾害危险性评估有什么区别,这两个一般是一起做的吗? 条件反射一旦建立,反射弧就不变了吗?当无关刺激与非条件刺激在时间上多次结合以后,原来的无关刺激就成为条件刺激,所以条件反射是在非条件反射的基础上.借助于一定的条件想过一定的 用化学来解释长江三角洲的形成 关于卤素和铁元素化合物颜色的问题1、卤素单质在单独存放时、水溶液中、四氯化碳中的颜色分别是什么?2、二价铁元素、三价铁元素的离子、氢氧化物颜色 条件反射和反射弧之间的区别.如果在做题中,什么时候应该用条件反射,什么时候用反射弧?请各位不要复制一大片相同的答案.纯纯的菜鸟---我在此先感谢各位. 电热管在通电时间长的情况下功率会逐渐下降怎解?铁铬铝合金热丝 和 镍铬合金热丝 产品寿命测试(3000h),测试前功率和测试完3000h功率变大还是变小?两种发热有怎么样的变化, 给玻璃磨边能用挫刀吗?就是哪种平锉. 条件反射有反射弧吗 电热管通电后颜色变红的本质是什么 长江三角洲形成的原因 非条件反射与条件反射的反射弧相同吗?它们分别是什么 日本同一姓氏可能有不同读音吗?发现这样一个问题:中岛美嘉的姓读Nakashima中岛早贵的姓读Nakajima以前只知道日本人的名字同字可能有不同的读音,难道日本人的姓氏同字也可以有不同的读音 铁的某种化合物中的铁元素与硫元素的质量比为7比8,则该化合物的铁元素与硫元素元素个数的比为 条件反射和非条件反射都需要完整的反射弧吗 浙江哪几家机构能做《地质灾害评估报告》? 一般吧台下面用的磨边玻璃镜子的价钱是多少?菱形的玻璃镜子…… 请简述长江三角洲城市带形成的自然原因.如题,谢谢啊~
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘