Example of how to optimize Ruby code to speed up the program running, ruby speed

Source: Internet
Author: User

Example of how to optimize Ruby code to speed up the program running, ruby speed

This article mainly introduces how to increase ruby gem contracts. ruby speed by 10 times.

Contracts. ruby is used in my project to add code contracts to Ruby. It looks like this:

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

As long as the add method is called, parameters and return values are checked.

20 seconds

This weekend, I tested the database and found that its performance was very bad:


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

Therefore, when a function is added with the contract function, the running speed is significantly reduced (about 40 times). I have studied this in depth.

8 seconds

I have made great progress. When passing the contract, I call the success_callback function, which is an empty function. The following is the entire definition of this function:

def self.success_callback(data)end 

Previously, function calls were very expensive in Ruby. Only deleting this call saves 8 seconds:

The call time for deleting some other attachment functions starts from 9.84-> 9.59-> 8.01 seconds, and the database speed is immediately doubled.

Now, things are a little complicated.

5.93 seconds

There are many ways 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 contract type. Based on the contract type, I can do different things. By changing it to the if statement, I save some time, but every time I call this function, I still spend unnecessary time checking the decision tree carefully:

if contract.is_a?(Class)
 # check arg
elsif contract.is_a?(Hash)
 # check arg
...

When defining a contract and building lambda, only one check is performed on the tree:

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

Then, I will completely bypass the logic branch and pass the parameter to the pre-calculated lambda for verification, thus saving 1.2 seconds.


Pre-Calculation of some other If Statements saves about 1 second:


5.09 seconds

Converting .zip to. times saves me one second:


Proof:

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

The above code is slower than the following one:

args.each_with_index do |arg, i|

It is slower than the following one:

args.size.times do |i|

. Zip takes unnecessary time to copy and create an array. In my opinion, the reason why. each_with_index is slow is that it is subject to. each, so it involves two restrictions, not one.

4.23 seconds

Next, let's look at the details. When the contracts Library is working, it will add a new class_eval (class_eval is faster than define_method) method for each method, this new method has a reference to the old method. When a new method is called, it checks the parameters, calls the old method based on the parameters, and then checks the return value and returns the value. All of these methods call the check_args and check_result methods of the Contract class. I canceled the call of the two methods and checked the new method correctly. The result saved 0.9 seconds:


2.94 seconds

As mentioned above, I have explained how to create lambda based on the Contract type and then use these to test the parameters. Now I have changed the method and replaced it with the generated code. When I use class_eval to create a new method, it will get the result from eval. A terrible vulnerability, but it avoids a lot of method calls and saves 1.25 seconds:


1.57 seconds

Finally, I changed the method of calling the rewrite method. I used to use references:

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

It saved another 1.4 seconds. I don't know why aliaa_method is so fast. I guess it's because it skips a method call and binds it to. bindbind.


Result

We successfully optimized the time from 20 seconds to 1.5 seconds. I don't think there is any better result. The test script I wrote indicates that a encapsulated add method is three times slower than the conventional add method, so these numbers are enough.

To verify the above conclusion, it is very simple. The reason why a lot of time is spent on calling methods is only three times slower. Here is a more realistic example: a function reads a file 100000 times:


A little slow! The add function is an exception. I decided not to use the alias_method method, because it contaminated the namespace and these alias functions will appear everywhere (documents, IDE Automatic completion, etc ).

Other reasons:

Calling methods in Ruby is slow. I like to modularize and reuse the code, but it may be time to inline more code.
Test your code! The time for deleting a simple unused method is reduced from 20 seconds to 12 seconds.

Other attempts

1. Method Selector

The method selector feature is missing in Ruby 2.0, Or you can write it like this:

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

This may make it easier to write decorator and speed up the operation.

2. Keyword old

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

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. Use redef to redefine the method:

Matz once said:

To eliminate alias_method_chain, we have introduced Module # prepend and prepend with the # sign before it, so we have no chance to add the redundancy feature to the language.

So if redef is redundant, maybe prepend can be used to write decorator?

4. Other implementations

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.