脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Ruby - 优化Ruby代码使程序运行速度提高的例子

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

2020-04-25 14:27脚本之家 Ruby

这篇文章主要介绍了优化Ruby代码使程序运行速度提高的例子,文中引用的项目把一个Ruby项目的运行时间从20秒优化到1.5秒,效果非常明显,需要的朋友可以参考下

这篇文章主要介绍了我是如何把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秒

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

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

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

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

8秒

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

?
1
2
def self.success_callback(data)
end

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

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

删除其它一些附件函数的调用,时间花费开始从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秒时间。

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

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

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

5.09秒

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

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

结果证明:

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

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

2.94秒

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

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

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。

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

结果

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

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

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

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

其它原因:

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

    剖析 Ruby 访问控制

    前面,我们说 Ruby 没有函数,只有方法.而且实际上有不止一种方法.这一节我们介绍 访问控制 (accesscontrols). 想想当我们在最高层而不是在一个类的定义里定义...

    ruby教程网3572020-04-08
  • RubyRuby迭代器的7种技巧分享

    Ruby迭代器的7种技巧分享

    这篇文章主要介绍了Ruby迭代器的7种技巧分享,Ruby中的迭代器非常人性化,本文既是讲解了7个技巧也是讲解了7种迭代器,需要的朋友可以参考下 ...

    脚本之家4782020-04-20
  • RubyRuby环境下安装使用bundler来管理多版本的gem

    Ruby环境下安装使用bundler来管理多版本的gem

    这篇文章主要介绍了Ruby环境下安装使用bundler来管理多版本的gem的方法,举了Ruby On Rails中的应用实例来进行演示,需要的朋友可以参考下 ...

    日拱一卒4332020-05-10
  • Ruby简要说明Ruby中的迭代器

    简要说明Ruby中的迭代器

    这篇文章主要介绍了Ruby中的迭代器,迭代器的概念在动态语言的编程中十分重要,文章中介绍了Ruby中的each迭代器和collect迭代器,需要的朋友可以参考下 ...

    goldensun2772020-04-25
  • RubyCentOS中配置Ruby on Rails环境

    CentOS中配置Ruby on Rails环境

    经过一个上午的折腾,终于把ROR环境在CentOS中搞定,绕了很多弯路,把文章写下来总结一下 ...

    可乐加糖4762020-04-12
  • RubyRuby进行文件信息输出实例代码

    Ruby进行文件信息输出实例代码

    Ruby进行文件信息输出实例代码,数据是随机的,所以每次的记录都会不同。 ...

    ruby教程网2962020-04-10
  • RubyRuby简洁学习笔记(一):字符串、数字、类和对象

    Ruby简洁学习笔记(一):字符串、数字、类和对象

    这篇文章主要介绍了Ruby简洁学习笔记(一):字符串、数字、类和对象,本文是学习笔记第一篇,需要的朋友可以参考下 ...

    脚本之家2472020-04-20
  • RubyRuby设计模式编程中使用Builder建造者模式的实例

    Ruby设计模式编程中使用Builder建造者模式的实例

    这篇文章主要介绍了Ruby设计模式编程中使用Builder建造者模式的实例,建造者模式将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表...

    范孝鹏2192020-05-07