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

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

周末,深圳微信开发者大会等您来!(参会必读+名单) 营造云安全生态链 第五届云安全联盟高峰论坛在京顺利召开 中国大数据六大技术变迁记 【最具价值CTO评选秀】当当网CTO熊长青:结合行业及技术发展,规划技术方向 社交颠覆传统,环信技术交流沙龙纪实(组图) 揭秘经典案例炼成之道 微信开发者大会精华回顾 从一家电子商务网站学到的经验教训 【最具价值CTO评选秀】极路由CTO康晓宁:极客转型CTO的感悟 盘点这些年来的“微信万能论” 《近匠》亲加通讯云:IM即时通讯API的极致服务者 TCL杯HTML5智能电视开发大赛完美落幕,8项获奖应用大揭晓! Xcode 6.2 Beta发布,支持WatchKit的iOS 8.2 SDK来了! 【最具价值CTO评选秀】携程高级技术副总裁叶亚明:从呼叫中心到移动互联网的演进 为什么靠谱的公司都应该参加CTO评选? 【深度剖析】微软开源.NET:战略重心已从PC转移到云端 专访POWER 8编程挑战赛选手黄文超:非科生的编程算法之路 BDTC 2014干货精彩不断,四大全天技术论坛议题全面曝光 全功能API!开源2D游戏引擎Firmament 微信公众平台新增“微信连Wi-Fi”功能,打通线上线下闭环 马化腾:微信在一线城市活跃 QQ增长从城市到农村 承包MIPS开发!Imagination发布双Codescape MIPS SDK 不任性了!Google Play宣布开通国内开发者通道 脑洞大开!用Swift写个Material Design组件库 美国最大婚恋网站eHarmony利用Hadoop、OpenStack重构自己 每个Java开发者都应该知道的5个JDK工具 异构计算应用研讨暨OpenCL编程培训在上海和西安成功举行! APIs.io:用来发布和发现API的利器 偏爱MySQL,Nifty使用4个Web Server支撑5400万个用户网站 直接拿来用!十大Material Design开源项目 初探WatchKit,开发者必须知道的6件事! 【MDCC专访】环信CEO刘俊彦:音视频即时通讯将成为技术壁垒 东日论坛新开张 关于异常的问题,请各位大哥帮帮忙!(50分) 我有很多数字,如何以固定的间隔和行宽在打印机完整地上输出? 学过C++,想学java,难吗?多久能上手? 如何设置系统的日期,时间? 我的网卡的驱动一直装不上? 在下请问,在游戏中实现人物的移动有哪几种方法? 关于地址栏的显示问题 老大们来看看! 今天高兴,散分,先进者自有大大的好处 对不起网管我没看清楚发错了地方请您帮忙转到java区里吧谢谢你了! my oracle8.1.7 on redhat7.2进不去,请高手指教 关于打印机不能够正常打印问题 关于XP的时间问题? 哪位知道在VC下通过敲击键盘模拟钢琴键发音——通过声卡在音箱中实时发出声音 本人泡MM精华之作,大家快看!! 互联网上控件的安全性问题 SDK的疑惑? gluLookAt()函数的用法 什么是类如何来理解应用它!!!看书看了不是很理解 第一次来csdn 高兴,散分,第一个进入者得! 怎样从ACCESS数据库中读出jpeg,gif,bmp...图片? web的验证如何与数据仓库验证一致?请高手看看 我用JBUILDER作的JPUPUPMEUN show(jpane,getx,gety) 的显示位置老在鼠标下一大截,在JPANE的左上角击鼠标右键出现多余显示 请问如何作封面? 国内有自由软件开发组织吗? 如何永久的挂载非linux分区? 高手请进! 在线急等!明天要交任务了! 关于异常的问题,请各位大哥帮帮忙!(50分) VFP报表问题 help!!! 关于listview控件内实现多个选项(图标)拖动的问题? 如何跟改X-WINDOW服务 如何用API实现向另一个程序的窗体里发送信息 关于listview控件内实现多个选项(图标)拖动的问题? help!! 一个关于临时表的储存过程的问题 java中都有那几大组件呀各有什么用处? 江湖救急!人事工资系统 这个函数看不懂!!! 问一个大家都想问的问题:xml的网页如何保存 问一个大家都想问的问题:xml的网页如何保存 关于CRYPTAPI(加密API)的问题,大家一起来讨论 请教:大家对于通过MODEM拨号抄收数据都是怎么做的?是使用控件还是使用DLL?,使用MSCOM控件吗? oince新图片 请教各位大虾,像这样的函数怎样去写呢?请您进去看看好吗? 如何在tc2.0中实现一种界面。像tc在dos下的安装界面。 OneraseBkgnd(..) Nonzero if it erases the background; otherwise, it is zero.不明白这个意思,我看程序改变了返回值,没什么变化。 现在<进销存软件>多如牛毛,可老板还要我们两个程序员在两个月内开发一个通用的该类软件,请大家给我点建议好吗? winmm.dll? 关于LINUX启动的一个问题? 黄铜和紫铜怎么焊接 耐高温塑料的定义,都具体有哪些? 一种什么试剂可以使氯化铁和典化钾反应的溶液变为血红色 从哪里可以查到尿素,复合肥的价格?快种麦子了,我想进一批尿素及复合肥,请知道的朋友告诉我,我是江苏的 在酸性溶液中Mg2+,NH4+,NO3-,cl-为什么可以大量共存酸性溶液中不是有大量的H+么,H+和NO3-不是结合成HNO3,HNO3不是应该把cl-氧化为氯气么?为什么不是这样,为什么可以共存有强氧化性就应该把cl-氧 银合金能够直接焊接到铜上面吗如果可以,用无铅焊料可以吗,还是需要银焊 黄铜与紫铜焊接需要注意哪些问题? Mg2+ K+ Cl- NO3- 酸性溶液中能大量共存么,碱性呢 能用于区别FeCl3溶液和溴水这两种棕黄色的液体的试剂是能用于区别FeCl3溶液和溴水这两种棕黄色的液体的试剂是a.Fe粉b.KSCNc.H2Od.淀粉KI溶液可是我觉得加入a FeCl3会变成浅绿色 而溴水没有反应 尿素是不是复合肥? 氢离子和碳酸根 氢离子和碳酸氢根 哪个反应快为什么 银钨合金与铜基焊接用什么工艺最好 尿素是复合肥吗 如何使物体溶解的快?还要一种!只要一种! 除了甲醛和甲酸,甲酸甲脂会发生银镜反应吗? 碳酸根和碳酸氢根同时存在时氢离子优先和谁反应?既然是在溶液中,我觉得氢离子也有碰到碳酸氢根的时候啊.为什么不能结合直接产生二氧化碳和氢气呢?类似于这样的反应还有哪些? 如何使冰融化更快?拜托了各位 谢谢 请问为什么大电流 电器的触头都要焊接上一块银的合金触点呀?我都有点听不懂这句话是什么意思. 怎样还原PH=8碱性硫脲溶液的金?(加金属铝 溶解的快与慢还跟那些因素有关? 铜材焊接银合金触头后如何冷却比较好?自然冷却?水冷? 电解硫脲溶液会产生什么会产生H2S吗?可以的话,麻烦写个方程式, 用班氏试剂鉴别健康人的下列四种液体,出现红黄色沉淀的是 A.血清用班氏试剂鉴别健康人的下列四种液体,出现红黄色沉淀的是 A.血清 B.唾液 C.胃液 D.尿液 为什么? 铜芯线与镍洛合金采用什么焊接方法? 焊接不锈钢与紫铜选什么样的焊丝 若用本尼迪特试剂对人的尿液进行鉴定出现了红黄色沉淀,是否可以说明此人患有糖尿病?为什么? 双缩脲试剂是检验蛋白中的肽键还是肽链啊?假如把豆浆煮沸了还能检验吗?如果能是为什么? 铝盐溶液与强碱溶液反应离子方程式铝盐过量 强碱过量 紫铜电焊条能焊铜管吗? 什么牌子的塑料杯好? 用石灰水小苏打,淀粉和食醋等不能完成的实验是?A 碘盐中碘的检验.B暖瓶中水垢的除去.C 食盐和纯碱的鉴别.D 鸡蛋壳主要成分的检验. 碳酸根、碳酸氢根、氢离子、氢氧根离子之间的共存关系一直搞不清楚这四种离子之间的共存关系.不知道哪些要反应哪些不反应...还望大虾指教... 市面上的养生杯什么塑料材质的,经常高温加热会不会有毒,比较放心的品牌有什么? 活性炭是酸性还是碱性 三聚氰胺能与双缩脲试剂产生颜色反应吗? 乐扣乐扣玻璃保鲜盒有木有人知道,这怎么样 活性炭中PH大于七代表的是酸性还是碱性 如何解释氨的碱性比水强?氨分子比水分子更容易跟氢离子结合,请用原子轨道杂化方面去解释一下! 乐扣乐扣玻璃保鲜盒好用吗?我在广西乐思购那里看到有这个产品介绍,不过不知道好不好用,麻烦用过有朋友帮解答一下, CS2与Na2S溶液一起振荡,溶液有无色变成有色, 氢离子 先和 氢氧根反映 还是先和碳酸根反映 why 大家为什么说乐扣乐扣玻璃保鲜盒更好呀? 困惑:FeCl3溶液和Na2S溶液FeCl3溶液和Na2S溶液混合后为什么不会发生双水解啊?生成Fe(OH)3和H2S气体?即使硫离子将3价Fe离子还原了怎么还会生成FeS呢?这和溶液呈酸性或碱性又有什么关系啊?谁能详 为什么氨的碱性比水强?用电子理论解释 能发生银镜反应的官能团 FeCl3溶液与Na2S溶液反应当FeCl3过量时会生成Fe2+和s↓ 为什么当Na2s过量时会生成FeS↓和s↓ 为什么 碳酸根与氢离子反应?与氢氧根反应吗?为什么碳酸氨能与碱反应放出氨气.同样也是碳酸根 乐扣乐扣的玻璃保鲜盒如何, AgNo3溶液与Na2S溶液反应 银和一水和氨生成什么 哪些官能团能发生银镜反应?我只知道醛基 如提,Na2S溶液与HCl溶液反应的现象 以及反应的化学方程式, 为什么碳酸根离子不和氢氧根离子反应?二氧化碳不和氢离子反应? 葡萄糖中含有哪些官能团,由于具有哪个官能团,能发生银镜反应?(说明过程与理由) 复合肥好还是尿素好? 氢离子、氢氧根离子、碳酸根离子、碳酸氢根离子 之间相互反应的所有离子方程式 有那位高手知道? 判断:尿液中含有尿素而汗液中不含有尿素.( ) 说明原因. 每袋尿素25千克,每袋复合肥50千克.要运40袋尿素和60袋复合肥,2吨、5吨、10吨的卡车哪个比较合适? 乐扣运动杯能放在微波炉里加热吗? 氯化铁的水溶液与什么试剂作用时,可以呈现a.红棕色沉淀;b.先呈血红色沉淀,后变为无色溶液;c.深蓝色沉淀
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘