阅读更多

0顶
3踩

研发管理

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

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

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

1
2
3
4
Contract Num, Num => Num
def add(a, b)
  a + b
end

只要add方法被调用,参数和返回值都会被检查。

 

20秒

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

                                   user     system      total        real
testing 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函数,该函数是个空函数,下面是这个函数的整个定义:

1
2
def self.success_callback(data)
end

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

                                  user     system      total        real
testing 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语句,我节约了一些时间,但每次调用这个函数时,我仍然耗费了不必要的时间在仔细检查这个判定树上面:

1
2
3
4
5
if contract.is_a?(Class)
  # check arg
elsif contract.is_a?(Hash)
  # check arg
...

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

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

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

 

                                  user     system      total        real
testing 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        real
testing 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        real
testing add                      0.510000   0.000000   0.510000 (  0.507554)
testing contracts add            5.090000   0.010000   5.100000 (  5.099530)

结果证明:

1
args.zip(contracts).each do |arg, contract|

上面的代码要比下面这个慢:

1
args.each_with_index do |arg, i|

要比下面这个更慢:

1
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        real

testing                         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秒

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

1
2
3
4
5
6
7
8
# simplification
old_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方法:

1
2
3
4
5
6
alias_method :"original_#{name}", name:"original_#{name}", name
class_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        real
testing 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        real
testing 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里缺少方法选择器这一特性,否则你还可以这样写:

1
2
3
4
5
6
7
8
9
10
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
  endend
endend

这样可能会更加容易编写decorator,并且运行速度也会加快。

2.关键字old

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

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo Foo
  def bardef bar
    'Hello''Hello'
  endend
end end
 
class Fooclass Foo
  def bardef bar
    old + ' World'' World'
  endend
endend
 
Foo.new.bar# => 'Hello World'Foo.new.bar # => 'Hello World'

3.使用redef重新定义方法:

Matz曾说过:

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

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

4.其它实现

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

Via adit.io

来自: csdn
0
3
评论 共 6 条 请登录后发表评论
6 楼 dohkoos 2014-06-29 00:13
dohkoos 写道
发一个不是软文的。

我是如何让Ruby程序速度提升189倍的
http://codemany.com/how-i-made-ruby-script-10x-faster/


代码有点小问题,稍微修改了下,只能达到156倍了。
http://codemany.com/how-i-made-ruby-script-156x-faster/
5 楼 dohkoos 2014-06-28 21:07
发一个不是软文的。

我是如何让Ruby程序速度提升189倍的
http://codemany.com/how-i-made-ruby-script-10x-faster/
4 楼 houyujiangjun 2013-09-06 23:23
软文,大失水准
3 楼 zhangzhenjj 2013-09-06 17:15
        
2 楼 紫絮流苏 2013-09-06 15:02
标题太具欺骗意义了
1 楼 kidneyball 2013-09-06 07:34
话说,原文标题叫 How I Made My Ruby Project 10x Faster。

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Oracle OCP 10G官方3.0版教材两本

    Oracle OCP 10G官方3.0版教材-Oracle Database 10g-Administration Workshop 两本分别适合官方要求的042和043考试

  • oracle 韩思捷_ORACLE数据库技术实用详解:教你如何成为10g OCP

    本书系统全面地介绍了Oracle10g数据库的结构、管理和优化,内容涵盖整个10gOCP的考试范围。包括Oracle10g数据库的体系架构、存储结构、网络服务、备份与恢复、闪回(flashback)管理、资源管理、调度管理、自动存储管理(ASM)、自动化性能监控与调优等。既着重介绍了在实际工作中需要用到的管理知识,又兼顾了OCP考试所涉及的考点。本书编排由浅入深,既详细介绍了Oracle10g数据...

  • oracle 10g OCP

    <br />Reference : http://education.oracle.com/pls/web_prod-plq-dad/db_pages.getpage?page_id=151<br /> <br />1z0-047 Oracle Database SQL Expert<br /> <br />Book<br />Oracle Database 10g: SQL<br /> Fundamentals I<br /> Electronic Presentation<br />D17108GC

  • oracle 10g ocp 047解析(精简版),ORACLE 10G OCP 043 笔记

    如题第1章 配置恢复管理器Configuring Recovery Manager组成:GUI或者命令行、可选的恢复目录、RMAN命令和脚本,以及磁带媒介连接考虑事项:RMAN是一种物理备份方法。其他的备份恢复方法:用户管理和Oracle EXPORT工具(逻辑备份,一般不做为独立得备份方法,而是对RMAN或者用户管理的备份提供附加的备份护,exp,expdp)可以使用控制文件或者恢复目录作为资料...

  • 个人需要,收集了一些关于Oracle 10g OCP认证的资料,与大家分享

    Oracle_10g_DBA_考试说明.pdf 10gDba1-041官方教材.rar 10gDba2-042官方教材.rar OCP10g OCP考试题库PDF OCP10g_OCP考试题库EXE Oracle 10g 原厂培训PPT

  • oracle 10g ocp 047解析(精简版),Oracle_10g_OCP_047解析(精简版).pdf

    QQ 清江石声明:对于答案的相关的说明,是个人对 Oracle 的理解和收集相关资料整理,供大家参考学习。2011-08-02说明: 127与196(已经删除)相同 108与234(已经删除)相同 91与205(已经删除)相同125与264(已经删除)相同 13与255(已经删除)相同 43与156(已经删除)相同72与216(已经删除)相同 ...

  • Oracle 10g OCP 042 题库 121-166题 共168题

    <br />121. You want to create a new optimized database for your transactional production environment to be used by a financial application. While creating the database, you want the Oracle software to take care of all basic settings to optimize the databas

  • Oracle 10g OCP 043 题库 91-140题 共185题

    91. Consider the following command to add a new disk group called "tdgroupA" with two failover groups:  CREATE DISKGROUP tdgroupA NORMAL REDUNDANCY  FAILOVERGROUP control01 DISK  '/devices/A1',  '

  • oracle OCP 认证教材链接地址

    为了方便记录自己学习的几个

  • ORACLE 10G OCP 043 笔记(一)

    第1章 配置恢复管理器Configuring Recovery Manager     组成:GUI或者命令行、可选的恢复目录、RMAN命令和脚本,以及磁带媒介连接考虑事项:RMAN是一种物理备份方法。其他的备份恢复方法:用户管理和Oracle EXPORT工具(逻辑备份,一般不做为独立得备份方法,而是对RMAN或者用户管理的备份提供附加的备份保护,exp,expdp)     TABLE 1 ...

  • 学无止境:了解主流的DBA认证考试

    目前在数据库领域,比较主流的认证考试包括了Oracle的OCA、OCP和OCM;微软的MCTS和MCITP;另外改用开源的MYSQL认证CMA、CMDEV和CMDBA。DBA在工作中用到的数据库大都来自Oracle、微软、...

  • 如何查看Oracle DBA认证都有哪些内容及考试

    <br /><br />    最近在准备参加一些oracle培训考个ocp认证,但google了很多遍也没有发现一份详细,权威的课程介绍及考试内容,今天在oracle university里仔细浏览了一遍才发现位于"View all certification paths"的链接中,隐藏得很深啊.特此作个记录以方便后来人查找吧.列表页面位于http://education.oracle.com/pls/web_prod-plq-dad/db_pages.getpage?page_id=141, 里面列出了

  • ORACLE 10G OCP 043 笔记(二)

    第9章 理解自动存储管理Understanding Automatic Storage ManagementASM is a cluster file system that can be used either with stand-alone Oracle instances or with Oracle Real Application Clusters (RAC) to provide a...

  • oracle 10g ocp证书终于拿到手

    自从早两年就通过了1z0-042和1z0-043的考试,见以下链接: http://space.itpub.net/7199859/viewspace-374799 http://space.itpub.net/...

  • ORACLE10g-OCP官方教材(第三版).pdf

    1.ORACLE10g-OCP官方教材I(第三版).pdf 2.ORACLE10g-OCP官方教材II(第三版).pdf

  • 史上最完整官方Oracle OCP中文文教材

    史上最完整官方Oracle OCP中文文教材,快来下载吧,还等什么 ....

  • 教你如何成为ORACLE 10G OCP

    oracle 10g ocp教程 oracle 10g ocp教程 oracle 10g ocp教程 oracle 10g ocp教程 PDF扫描版

Global site tag (gtag.js) - Google Analytics