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

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

Sahara顺利毕业,将加速OpenStack与Hadoop的融合 十大将拯救地球的开源硬件项目 Android应用程序开发者需要了解的五个内容 百度轻应用插件CloudaMySQL诞生记 盘点Github所用的开源项目 开源不只是面对开发者 见证新一代全能大数据计算平台崛起 ——中国Spark技术峰会将于4月19日在北京召开 大佬放猛料的节奏 阿里云聚无线为开发者提供一站式服务 智能硬件又添新选手 汉王推出便携PM2.5检测仪 2014年首场百度轻应用高级训练营落幕 现场展示云端结合能力 销售易史彦泽:企业应用,自上而下的革命 专访寻医问药网创始人:轻应用背后的长尾效应 轻松打造品牌轻应用:实时Web App开发框架Clouda Intel强势入驻,Cloudera新一轮融资总额已达9亿美元 每个Web开发者必备的9个软技能 4月1日:闻名世界的苹果公司正式成立 AMD正在探索一种新颖的Linux开源驱动模式 QQ手机浏览器推出5.1播霸版 内置强解码引擎支持22种视频格式 Ceph浅析(上):概况与设计思想 IBM:你好,互联网 光网络SDN化的四大驱动力及两条路径 《近匠》酷传:解除App发布和监控痛苦 有道heX正式开源:用HTML5和NodeJS开发桌面应用 在3D空间创作:英大学生发明增强现实设计产品 2014年3月浏览器份额:Chrome终超Firefox 成市场第二大浏览器 2014年3月操作系统份额:Windows 7大涨,Windows XP仍未跌破27%大关 路透社:iPhone 6仅有4.7寸版本,将于5月量产,5.5寸遭冷冻 高端实战技术培训:OpenStack企业应用之路 据统计,Oracle已击败IBM跃居软件行业第二 【技术博客推荐】2014年值得关注的10个开源项目 Intel在深圳建立智能设备创新中心 将投入1亿美元 请问哪里有《Delphi 4 编程技术内幕》随书附带的源码下载啊? 请教:将可执行程序安装到用户机上后,用什么办法控制其它用户非法复制使用? 我想隐藏MSFlexGrid2的的10到最后行,但不行!!急救!! 哪有STL 和BOOST库的SOURCE CODE 当 如何做安捷生产管理系统。如何下手!!! PB8在链接Access2000时,为何会发生问题.链接后在PB8中打开只能看库中数据,却不能修改! 我用OCX控件编程,插入OLE控件时,出现“需要协议文件”,我该怎么办呢? waveoutopen! 问个问题,我一直有疑惑 BDE中建立的Access库,怎么才能打开; Access的密码为空,用户名不知道; 第四次帖了,高手在哪儿? 为什么我装了一个元件后再安装一个另外的,会把第一个冲掉? 就要去实施了,不知道什么时候才能回来,散分 我是菜鸟,问一个很菜的问题,!!!!!!!!! ATL中创建的ActiveX控件,是怎么在程序中使用的???????????? 一个关于LotusScript的一个问题。调用公试。 **请教:怎么让MSFlexgrid的一行显示一种颜色. 一个小问题,希望有人能解答! DLL高手请入,关于在DLL中引出对象的问题?????? 求教!!有关DLL调用中返回字符串类型出错的问题。 Delphi怎么实现子类化? session 和 cookie 各有千秋,区别在那里啊?敬请赐教! fm1008leaf(小幻姬(玉笛书生的徒弟)):我考考你就知道你是真是假! jneu(沧海桑田)怎么不理我?有关宏拷贝问题 paradox中的NUM类型在sql语句中是什么类型?为什么asfloat会出错? 关于J2EE的就业前景,值不值的花二万元参加一个二十天的培训,欢迎大家热烈讨论 在listbox 中,如果知道了一个Item的内容,怎么知道它的indix 今有同仁问到,三十以后咋过。恰巧心情郁闷,发上几句牢骚。 少年暗恋对象,早已嫁作人妇。那天遇见街头,孩子叫我叔叔。 OICQ问题! 如何可获得Table 内字段的类型及长度? windows2000 server 的internet 共享怎样设置权限 软件开发的请进来讨论,关于报酬的问题。大家多提建议。 1.为什么有些地方可以用MessageBox函数,有些地方不可以2.mfc的CView::OnFilePrint提供了打印的方法,要是一个程序没有视图要打印一般用什么方法? 请问在DEVELOP2000里面可以使用进度条控件吗?怎么编程实现? ???oracle监听器问题? WebBrowser控件获得网页中文本框的名称该如何写? CFtpConnection 怎样在FastReport中控制或判断所连接的数据库表到了eof? CTypedPtrList模板对象中,如何删除指定位置的元素??十分着急! ms sql server 的技术问题 怎样清除事务日志????????? 在那里改个人在cdsn.net注册信息(如名字,地秩)? 关闭窗口的事件如何触发?( 高手们请问deiphi如何实玩宏代换???????????十分火急!!!!!!! 请帮忙!!SOS!!! 请问如何实现宏代换????????????? 关于打印的一个不错的设想 我是新手,初次接触网络管理,请问要管理局域网需要具备哪些方面的知识,需要安装那些应用软件?有什么好书可以介绍吗?谢谢各位! 在VB中设计控件,其上放有uText1、Text2,当uText1失去焦点时,并不触发Validate事件 如何在程序中判断是否做了超级连接? 怎样使listview中的大图标左右居中,上下居中。 怎么样处理聊天室的退出问题? 血液循环的两条路线。还有关于心脏的详细知识。 ……! 硫酸银和含碘化钾的氯化钠反应吗硫酸银和氯化钠反应生成氯化银多还是和含碘化钾的氯化钠反应生成的氯化银多? 为什么氯化钠和硝酸钾溶液不反应请讲的详细一些 怎么分类:氯化钠,氧化钙,氢氧化铜 硫酸银放氯化钠沉淀快还是放碘化钾快 氯化钠溶液和硝酸钾溶液反应方程式. 配平的方法和质量分数的计算过程 氯化钠和稀硫酸能反应吗?为什么? 初中高温化学式怎么配平?比如fe3o4+co=高温fe+co2 请大家给我一些关于化学方程式的计算或配平的方法:如归一法.再给我...请大家给我一些关于化学方程式的计算或配平的方法:如归一法.再给我讲讲用法谢谢. 怎样快速记住化学公式 硝酸钾和氯化钠反映的化学方程式 鉴别 氯化钠和稀盐酸 除了加碳酸钙 还可以加 氧化铜么 氧化铜 和 氯化钠 会反应么 可以加 氢氧化铜么 都说明现象 原因4楼又给我点了一点 5楼又点了一点那么说 鉴别 氯化钠和稀盐酸 有4种 怎样记住化学表达式 氯化钠和硝酸钾是否能反映? 硝酸铵不溶于水吗?溶不溶于酸?氧化铜能不能跟氯化钠反应?硝酸铵好像溶于水,可硝酸铵固体呢?为什么硝酸铵固体和蔗糖可以用水鉴别? 化合价升降法配平~Br2 + NaOH = NaBr + NaBrO + H2O像这种Br的歧化反应,该怎么考虑化合价升降法配平 错了错了~是这个Br2 + NaOH------ NaBr + NaBrO3 + H2O 氯化钠能和硝酸钾反应吗?如果把氯化钠和硝酸钾混合溶液蒸干,应该会析出氯化钾、氯化钠、硝酸钾、硝酸钠四种晶体, 氢氧化铜和什么反应生成氧化铜和什么 用化合价升降法配平方程式Ca(ClO)2 + HCl = CaCl2 + Cl2 + H2O本人分不清Cl的化合价变化转移的.赐教 硝酸钾和氯化钠可以发生反应吗 为什么 化学方程式配平中的归一法要把哪一个化学式的化学计量数看作1随便举些例子,越多越好各位聪明的哥哥姐姐帮个忙 化合价升降法配平题4Fe+ +4NaO2 +6H2O =4Fe(OH)3 +O2 +8Na+资料上的答案有该方程,只是不知道怎么配平的?这个方程用化合价升降法是怎么配平的? 初二生物知识点1、下列不属于无性生殖的是( D )A 椒草的用叶繁殖 B 马铃薯的用块茎生殖C 月季的扦插 D 小麦的种子繁殖2、种子的胚是由( A )A 受精卵发育成的 B 受精极核发 硝酸溶液与氢氧化铜溶液能否发生复分解反应? 氯化钠与浓硫酸的反应方程式 初二生物知识点归纳(人教版) 加热氢氧化铜溶液会不会受热分解生成氧化铜黑色物质 (氢氧化铜是溶液哦) 求,氯化钠硝酸银反应方程式谁快给谁分 求初中化学有关反应条件是高温的所有化学式要在化学式前注明反应物的名称 在溶液中下列物质一般不易发生复分解反应的是 碳酸钾 氯化镁 硝酸钠 氢氧化铜 如何用化合价升降法配平 氯化钠与硝酸钾可以反应吗 高一必修一地理知识点总结 氧化还原反应的配平(一定用化合价升降法配)请高手绝对详细的配一下Fe+HNO3(稀)=NO2+Fe(NO)3+2H2O的配发,顺便问一下,用此法配的时候,电子一会儿要乘以一个数字,一会儿又不要乘了,这乘来乘 高一【上】地理第一章知识总结还有 要非常非常详细的 区分下列物质 氯化钠溶液 氯化铜溶液 氯化钾溶液 怎样用化合价升降法配平化学方程式? 高一必修一地理知识点总结要全最好带文件的 氢气能还原氧化铜为什么不能还原氢氧化铜和氯化铜? 初中所有的化学公式和反应现象和用途 高一地理必修一知识点完整总结(注重知识点间的联系) 何种金属与CuCl2水溶液反应产生氢气 为什么硫酸铜溶液不能与氯化钠溶液发生反应 高一地理必修一 一至三章知识点总结 高中化学中的化学方程式离子配平法是怎么配啊?化学离子配平法是怎么个配法啊,老师讲时没听懂. 硫酸铜与氯化钠不能反应,为什么? 为什么硝酸钾不易发生复分解反应 急需高一地理必修二知识点详细总结 请问,铜和 稀 硝酸的化学方程式用 离子配平法 怎么配平?RT, 氯化钠溶液与硝酸钾反应是不是复分解反应? 化学式配平习题 这个离子和化学方程式是怎么配平的, 配平我到是知道配~可是那些数字有什么意义啊? 给些初三习题我,上面习题 答案写在下面 就是习题一部分答案一部分· 分别将下列物质同时加到水中,得到无色透明溶液的是A氯化铁、氢氧化钾、氯化钠 B碳酸钠、氯化钾、C硫酸铜、盐酸、氯化钾 D、碳酸钙、氯化钠、硫酸钾 高中生物会考知识点446734181@qq.com 角码用圆圈+数字代替C②H⑤OH+O② - CO②+H②OFe②O③+CO - 高温 Fe+CO②CH④+O② - 点燃 CO②+H②OMg+CO② - 点燃 MgO+CCu②(OH)②CO③ -△ CuO+H②O+CO② 氯化钾与氯化钠反应吗?氯化钠溶液与氯化钾固体反映吗?
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn