Examples of optimizing Ruby code to make programs run faster _ruby topics

Source: Internet
Author: User
Tags case statement

This article mainly describes how I have raised Ruby gem Contracts.ruby speed by 10 times times.

Contracts.ruby is used in my project to add code contracts (contracts) to Ruby. It seems to be the same:

Contract num, num => num
def add (A, b)
 A + b
end

Whenever the Add method is invoked, both the parameter and the return value are checked.

20 seconds.

This weekend, I tested the library and found its performance very bad:

This is the result of running 1000 times after the random input.

So, when the contract function is given to a function, the running speed is significantly reduced (about 40 times times this), and I have studied it in depth.

8 seconds.

I made a lot of progress, and when I passed the contract, I called the Success_callback function, which is an empty function, and here's the whole definition of this function:

def self.success_callback (data) End
 

The original function call was very expensive in Ruby, and only the call was removed, saving 8 seconds:

Delete some other attachment function calls, the time spent starting from 9.84-> 9.59-> 8.01 seconds, the library speed immediately to the previous twice times.

Now, things are getting a little complicated.

5.93 seconds.

There are many years to define a contract: Anonymous (Lambdas), Class (classes), simple old data (plain ol ' values), and so on. I have a very long case statement to check the type of contract. On the basis of this contract type, I can do different things. I saved some time by changing it to an if statement, but every time I called the function, I still spent unnecessary time checking the decision tree carefully:

If contract.is_a? (Class)
 # check arg
elsif contract.is_a? ( hash)
 # check arg
...

When defining contracts and building a lambda, check the tree only once:

If contract.is_a? (Class)
 Lambda {|arg| # Check arg}
elsif contract.is_a? ( Hash)
 Lambda {|arg| # Check arg}

Then I would completely bypass the logical branch and validate it by passing parameters to the estimated lambda, saving 1.2 seconds.

It is estimated that some other if statements, almost 1 seconds to save:

5.09 seconds.

Converting. zip to. Times saves me 1 seconds:

The results show that:

Args.zip (contracts). Each do |arg, contract|

The code above is slower than the following:

Args.each_with_index do |arg, i|

Slower than the following:

Args.size.times do |i|

. zip takes unnecessary time to copy and create new arrays. And I think that. Each_with_index is slow because it is subject to the. Each, so it involves two limits instead of one.

4.23 seconds.

Next look at the details of things, contracts library at work, it will add Class_eval for each method (Class_eval faster than Define_method) New method, the new method has a reference to the old method, when calling the new method, it will check the parameters , and then call the old method based on the parameters, then check the return value, and return the value. All of these will invoke the Check_args and Check_result two methods of contract class. I canceled the calls to the two methods and checked the new method properly, saving 0.9 seconds:

2.94 seconds.

On top, I've explained how to create a lambda based on the contract type, and then use these to verify the parameters. Now, I've changed the method and replaced it with the generated code, and when I use Class_eval to create a new method, it gets the result from the eval. A terrible loophole, but it avoids a whole bunch of method calls and saves 1.25 seconds:

1.57 seconds.

Finally, I changed the way I called the Rewrite method, and I used the reference earlier:

# 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
}}

I made the changes and used the Alias_method 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
}}

Surprise, and save 1.4 seconds. I don't know why aliaa_method so fast, I guess because it skipped a method called and bound to. Bindbind.

Results

We succeeded in optimizing the time from 20 seconds to 1.5 seconds, and I don't think there is any better result. The test script I wrote shows that a encapsulated Add method is 3 times times slower than the regular Add method, so these numbers are good enough.

To verify the above conclusion is simple, a lot of time spent on the call method is only 3 times times slower reason, here is a more realistic example: a function to read a file 100,000 times:

A little bit slower! The Add function is an exception, and I decided not to use the Alias_method method anymore because it pollutes namespaces and these alias functions appear everywhere (document, IDE AutoComplete, and so on).

Other reasons:

Calling methods in Ruby is slow, I like to modularize and reuse code, but maybe it's time to get more code inline.
Test your code! Delete a simple unused method time from 20 seconds shortened to 12 seconds.

Other attempts

1. Method Selector

Ruby 2.0 lacks the feature of a method selector, otherwise you can write this:

Class Foo foo
 def bar:beforedef bar:before
  # 'll always run before bar, when Bar is called# would always run befor e Bar, when the bar is called
 endend

 def bar:afterdef bar:after
  # 'll always run after bar, where bar is called# W Ill always run after bar, when Bar are called
  # may or May are able to access and/or change bar ' s return value# Or May is able to access and/or the change bar ' s return value
 endend
endend

This may make it easier to write decorator and will run faster.

2. Key word old

Another feature lacking in Ruby 2.0 is the reference rewrite method:

Class Foo foo
 def bardef bar
  ' hello ' ' hello '
 endend end 

class Fooclass Foo
 def bardef Bar Old
  + "world" + ' world '
 endend
endend

Foo.new.bar # => ' Hello World ', Foo.new.bar # => ' Hello World '

3. Redefine the method using Redef:

Matz once said:

To eliminate Alias_method_chain, we introduced the front Plus # of the module#prepend,prepend so that there was no chance of adding redundancy to the language.

So if Redef is a redundant feature, maybe prepend can be used to write decorator?

4. Other implementation

So far, these have been tested in Yarv.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.