优化Ruby代码使程序运行速度提高的例子_ruby专题

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

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

Contract Num, Num => Num
def add(a, b)
 a + b
end

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

20秒

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

这是在随机输入下,运行1000次以后的结果。

所以,当给一个函数加入合约功能后,运行速度明显下降(约40倍这样),对此,我进行了深入的研究。

8秒

我取得了较大的进展,当传递合约时,我调用success_callback函数,该函数是个空函数,下面是这个函数的整个定义:

def self.success_callback(data)
end 

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

删除其它一些附件函数的调用,时间花费开始从9.84-> 9.59-> 8.01秒,该库的速度马上提升到以前的两倍了。

现在,事情变的有点复杂了。

5.93秒

这里有许多年种定义一个合约的方式:匿名(lambdas)、类 (classes)、简单旧数据(plain ol' values)等。 我有个很长的case语句,用来检测合约的类型。在此合约类型基础之上,我可以做不同的事情。通过把它改为if语句,我节约了一些时间,但每次调用这个函数时,我仍然耗费了不必要的时间在仔细检查这个判定树上面:

if contract.is_a?(Class)
 # check arg
elsif 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秒时间。

预计算一些其它的If语句,差不多又节省了1秒时间:

5.09秒

将.zip转换为.times又为我节省了1秒时间:

结果证明:

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

2.94秒

在上面,我已经解释了如何基于Contract类型创建lambda,然后使用这些来检验参数。现在,我换了种方法,用生成代码来替代,当我使用class_eval创建新方法时,它就会从eval中获得结果。一个可怕的漏洞,但它避免了一大堆方法调用,并且节省了1.25秒:

1.57秒

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

# 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方法:

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。

结果

我们成功的将时间从20秒优化到1.5秒,我不认为还有比这更好的结果的了。我所编写的 这个测试脚本表明,一个被封装过的add方法要比常规的add方法慢3倍,所以这些数字已经足够好了。

想要验证上面的结论很简单,大量的时间花在调用方法上是只慢3倍的原因,这里有个更现实的例子:一个函数读一个文件100000次:

稍微慢了点!add函数是个例外,我决定不再使用alias_method方法,因为它污染了命名空间,并且这些别名函数会到处出现(文档、IDE的自动完成等)。

其它原因:

    在Ruby中调用方法很慢,我喜欢将代码模块化和重复使用,但或许是时候将更多的代码进行内联了。
    测试你的代码!删掉一个简单的未使用的方法时间从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
 endend
endend

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

2.关键字old

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

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做过测试。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索ruby
ruby例子、rails on ruby例子、ruby 优化、lte下载速率优化专题、arm neon 优化例子,以便于您获取更多的相关知识。

时间: 2024-10-23 03:47:43

优化Ruby代码使程序运行速度提高的例子_ruby专题的相关文章

提升Ruby on Rails性能的几个解决方案_ruby专题

简介 Ruby On Rails 框架自它提出之日起就受到广泛关注,在"不要重复自己","约定优于配置"等思想的指导下,Rails 带给 Web 开发者的是极高的开发效率. ActiveRecord 的灵活让你再也不用配置繁琐的 Hibernate 即可实现非常易用的持久化,Github 和 Rubygems 上丰富多样的 Rails 插件是 Rails 开发高效率的又一有力保障.Rails 是一个真正彻底的 MVC(Model-View-Controller) 框

ruby 简单例子_ruby专题

让我们写一个计算阶乘的函数.对于阶乘的数学定义如下: n! = 1               (当 n==0 时)    = n * (n-1)!       (其它情况) 在Ruby里,可以这样来写: 复制代码 代码如下: def fact(n)          if n == 0            1          else            n * fact(n-1)            end        end   你可能会发现 end 的反复出现,正因为如此,Ru

Ruby rails 页面跳转(render和redirect_to)_ruby专题

Ruby代码 复制代码 代码如下: if @user.update_attributes(:password => params[:user][:password]) flash[:notice] = '密码修改完成' redirect_to :action => 'index' else redirect_to :action => 'change_pass', :id => @user end 后来随手改了下第5行,把redirect_to改为render,居然就OK了.网上找

Ruby中百分号和字面值的使用示例_ruby专题

    需要插值与嵌入双引号的单行字符串使用 %() (是 %Q 的简写).多行字符串,最好用 heredocs . # bad (no interpolation needed) %(<div class="text">Some text</div>) # should be '<div class="text">Some text</div>' # bad (no double-quotes) %(This is

优化Python代码使其加快作用域内的查找_python

我将示范微优化(micro optimization)如何提升python代码5%的执行速度.5%!同时也会触怒任何维护你代码的人. 但实际上,这篇文章只是解释一下你偶尔会在标准库或者其他人的代码中碰到的代码.我们先看一个标准库的例子,collections.OrderedDict类:   def __setitem__(self, key, value, dict_setitem=dict.__setitem__): if key not in self: root = self.__root

ruby开发的交互式程序例子_ruby专题

实现pry,ronin,msf类似的终端下交互功能,这里主要用到了ripl gem: 1.能够调用定义的方法,如help 2.能够执行系统命令 3.能够类似irb/pry进行ruby语言解析 安装: gem install ripl gem install ripl-shell_commands 示例 ripl这个gem,它也提供很多插件,如下是一个简单的终端下交互实现: require 'ripl' require 'ripl/shell_commands' require 'ripl/col

Ruby on Rails下的图像处理入门教程_ruby专题

图像可以说是任何应用至关重要的一部分.从社交网络到一个简单的Bug追踪器,图像都扮演着重要的角色.然而管理图像并不是一件容易的事情,需要提前耗费大量的时间精力去计划. 本文演示了如何在Rail中实现这一目标.如何处理你的图像以及在后台创建多个版本?如何通过压缩图像又不损图像质量,以此来提高页面性能?这些且听本文一一道来. 入门 本文教程是运行于Rails 4.2,通过MongoDb数据库和HAML呈现视图.不过本文所展示的片段应该兼容任何Rails版本(尽管有些配置差异). 布置舞台 Image

利用Ruby的SOAP4R编写SOAP服务器的教程_ruby专题

 什么是SOAP ? 简单对象访问协议(SOAP)是一个跨平台和语言无关的,基于XML的RPC协议,通常(但不一定)是HTTP. 它使用XML来编码信息使远程过程调用,HTTP在网络上从客户机到服务器来传输信息,反之亦然. SOAP有几个优势超过其他技术,如COM,CORBA等为例,其相对廉价的部署和调试成本,它的可扩展性和易于使用,存在几种不同的语言和平台实现. 请参阅出简单的教程了解 SOAP 本教程将熟悉SOAP实现Ruby(SOAP4R).这是一个基本的教程,所以如果需要深入细节,那么需

详解Ruby设计模式编程中对单例模式的运用_ruby专题

简介      单例模式是设计模式中最简单的形式之一.这一模式的目的是使得类的一个对象成为系统中的唯一实例.要实现这一点,可以从客户端对其进行实例化开始.因此需要用一种只允许生成对象类的唯一实例的机制,"阻止"所有想要生成对象的访问.使用工厂方法来限制实例化过程.这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义. 要点      显然单例模式的要点有三个:一是某个类只能有一个实例:二是它必须自行创建这个实例:三是它必须自行向整个系统提供这个实例.