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

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

在VB中引用dbf及索引文件 在VB中运行Microsoft Access报表的好方法 在VB中直接用ODBC API访问数据库 在数据库应用中, 经常要动态生成 Select 语句,典型的情况:-VB资料 在运行时修改DataEnvironment的连接字符串-VB资料 在抓取資料庫之資料前先計算資料總筆數 -VB资料 增加 MsFlexGrid 的编辑功能-VB资料 真正删除数据库的记录 -VB资料 用VB制作屏幕保护程序 VB多媒体程序设计 VB设计动画时钟 VB游戏写作技巧(1)秀图篇 VB游戏写作技巧(2)网络篇 VB中播放WAV文件 VB中播放三维动画编程一例 Visual Basic 5.0多媒体创作技巧 Visual Basic 5.0中实现视频画中画 Visual Basic 中制作流水灯 Visual Basic动画编程技术 Visual Basic环境下Video/ Audio压缩数据流播放技术 Visual Basic设计多媒体应用程序 Visual Basic影音控制 Web动画图像分解在VB动画设计中的应用 播放 .flc 动画 -VB资料 播放 AVI -VB资料 播放MP3文件 -VB资料 播放背景音乐 -VB资料 播放声音文件 -VB资料 播放资源文件文件中的声音-VB资料 不用ActiveX控件也能播放声音文件-VB资料 VB创建动态图标 菜鸟初学asp,请教学的方法 请问C++有哪几个方向可以发展 请问有哪个API可以播放MP3文件 急问:如何在MDI中把DLL当作子窗口调用? 请问C++有哪几个方向可以发展? 答谢帖,欢迎 anux(阿牛克斯),firetoucher(风焱) , 666333(刘六) , SmallHand(火龍) 刘海洋伤熊被判免予刑事处罚6月取得清华毕业证 sos,网络版的vb怎么搞啊,大家帮帮忙啊,。。。sos 请问C++有哪几个方向可以发展? 如何用ASP像读取数据库表一样读取EXCEL表内容? 答谢帖,欢迎 anux(阿牛克斯),firetoucher(风焱) , 666333(刘六) , SmallHand(火龍) 请问C++有哪几个方向可以发展? 高分急求高手:Sybase查询的汉字问题 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 帮我译vb为c# VB.net如何在进程之间通讯,我用SendMessage发WM_COPYDATA给目标窗口,可是总是不行,请高手帮忙! 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 关于文件操作的那几个函数问题 如何初始化const char * p[5]数据成员? 如何安装WINME???急!,在线等!!! C文件读写(急,来者有分) ---控件--里连接---数据库---都有什么方法?SQL2K 正在搜索 请稍后 是怎么做的? 为什么这样做? 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 再问,关于中文字符.急!!!! OleDbDataReader这个东西是不是不可以同时打开两个? 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 日期的计算问题 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 用jsp编打印图片的问题(很急) 谁第一个来,谁得分 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 今天生日,除了MM没人祝我生日快乐,只好自已祝福自已了(散分) 如何将表单提交到数据库中? 在线等!!!!!!!!!!!! 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 如何在屏幕任意位置显示移动的文字 收到了数字情书,我嫩是解不开。。帮帮我吧,不好回了。 如何让winamp慢速播放mp3? 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! 在服务器端的DLL文件中如果获取网站在服务器的磁盘上的路径? 在水晶报表中如何控制所显示记录的条数! 一个超级奇怪的iis问题??????? 一路同行?我开始学习C#, 我是一从为接触过单板的人,学的是软件。现在我想提一个问题。 如何解决UNIX通讯问题? 关于读出文件里的字符串与录入的字符串比较问题 哪位高手帮忙编写一个程序,使局域网各用户之间能传送文件!谢谢! help!有关SESSIONBEAN! 制作孵化机问题做孵化机用一个300W的带风机发热管恒温37.5度±0.5度,安装在哪个地方就变热均匀?能保证多大立方米的空间受热均匀?还是上下各装1个发热管? 孵化机是怎样构造的 孵化机漏电 怎么处理? 包含“惊”的成语第一个字是惊 第四个字是及 带有惊字的成语 养小蝌蚪的坏处,要10条以上的.我要参加辩论,我是反方. 暴跳如雷的意思急用 民族资本主义的特点 生命与非生命的本质区别不要和我说什么新陈代谢,应激性,等等因为那些都是表象而不是本质,我要的是本质,好好的思考一下,看看我和谁有思想共鸣哪?呵呵,我觉得8楼还行,但是和我的观点 rt我今天开学高一,中考成绩一般 进的普高,然后满分70的化学只考了30而且这次是因为考了很多基础题,按模拟考试的难度估计连20都考不上.那些酸碱盐、金属、溶液的东西几乎只会5%,公式记住 含“惊”的成语 挪威称应联合国要求将协助销毁叙利亚化美专家:进口产品及食品易导致美国儿童俄空降兵部队摸黑赶路不慎引爆炸弹 致日本“无性症候群”涌现 恐陷“绝种”报告称美国600万年轻人无所事事 不安倍7小时长会后品尝软柿子 满足称“法国媒体再曝美国监听法国驻美和联合国安倍称日本拟限制外国人在国防设施附近伊朗死囚受绞刑后未死 司法部长称没必数据显示日韩航路日本乘客骤减 创历史美影院拟设“手机短信专用区”防手机光大学怎么过?沪上家长寄语:该在大学里《家有仙妻》续集11月将开机 原班人香港科大学者:上海成立自贸区促香港寻今年月饼礼盒明显\"瘦身\" 礼盒里上海烟花节站票已卖完座票销售差 明年20位特级教师对话未来园 教师有责任旅游节开幕花车大巡游 沪部分道路将临中秋夜700航班在申城起落 乘客或可沪指大涨3.39%站上2200点 银我国首例自主研发完全可降解心脏支架在长宁:白领午餐食堂有望评星级
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘