Rails Application Performance Optimization

Source: Internet
Author: User
Tags assert benchmark joins memcached
Do you think your rails application is too slow to respond? This is a speech on the RailsConf2006 of Rails ' application performance optimization, which I hope will help you.
Before you optimize your application, we need to make the following points clear:
Blind optimization without first performing a performance test is very stupid. If your application is poorly designed because of unreasonable design, then I suggest you take the time to refactor your code instead of local optimizations, which will only make the problem more and more problematic. Before the optimization, it is best to set a goal for yourself, this prevents waste of time because of excessive optimization, and when you reach the desired goal, it's not necessary to optimize every page, just focus on the pages that are most frequently accessed, and perform continuous performance measurements during development, which helps you   Position performance bottlenecks while optimizing. After the optimization is completed, to evaluate the quality of our optimization, we need to first determine a set of performance parameters:
Latency, how much time throughput is required to respond to a request, how many request system utilization per second can be processed, and when a large number of requests need to be processed, does your system run at full capacity? Resource overhead, the cost of each request determines the performance parameters to be measured, and we need automated benchmarking (benchmark) tools to help us perform performance comparisons before and after optimization:
Rails log files (debug_level >= Logger::D ebug) Rails Log profiling tool (needs to output log to syslog) the Rails benchmark script (Script/benchmarker) database provides Performance Analyzer Apache Bench (AB or AB2) Httperf Railsbench can be downloaded in http://rubyforge.org/projects/railsbench/I recommend Railsbench, which measures the raw performance of rails to handle a request, is described in the following article on Railsbench.
In addition to benchmarking tools, you can also choose a simple performance testing tool:
Ruby Profiler Zen Profiler rubyprof Rails Profiler script Ruby performance Validator (commercial software, only Windows supported) In fact, however, the Railsbench has built-in performance testing tools, so there is little need to use these tools alone.
The tools have been taken care of, so let's start our optimization tour here.
In my experience, rails performance issues are generally focused on the following areas:
Slow helper method responsible for routing too much Union (associations) too much access to database slow session access

However, the performance of the database is largely out of the question, because the primary cost of connecting to the database is actually to create the ActiveRecord object.
This is the talk on the next, we will focus on the above several problems to give specific optimization plan.

=================================================================================

Before you begin, it is important to note that the optimization strategy is in fact not universal in most cases, because the hardware and software differences, user habits and so on may cause the same optimization strategy to get a completely different effect in different systems, of course, here are some general applicability of the strategy, But I suggest you do a comparison test when applying these strategies, just as you did at the beginning of the first lecture, not blindly optimizing.
Session Optimization
If your system needs to save individual session information (such as a shopping site) for each visitor, the access speed of the session will be a key factor affecting system performance, and currently available session Access strategies are:
Memory, fast, pretty fast. But if your application hangs up or needs to be restarted for some other reason, all session information will be lost, and this approach can only be used in a single app server application file system, it is easy to use, each session corresponding to a file, And can be easily expanded via NFS or NAS, but slower database/activerecordstore, with a simple (rails default policy), but slow database/sqlsessionstore, similar to the one above But with the original SQL instead of ActiveRecord, there is a certain improvement in performance, and the comparison of Sqlsessionstore and Activerecordstore can be read in this article memcached, slightly faster than Sqlsessionstore , scalability is better, but more difficult to get statistics, on the comparison between memcached and Sqlsessionstore, see this article Drbstore, in memcached unsupported platform, you can choose Drbstore, However, performance is less than memcached and does not support automatic session cleanup.Cache Optimization
Rails defaults to support the centralized cache method:
Pages, very quickly, the entire page is saved in the file system, Web server can bypass app server to complete the request response, but there are some inherent flaws, such as the inability to cope with the need for user login application Actions, the second fast, Cache controller action execution results, and because it can be invoked to controller filters, it is good to prevent unauthorized users from accessing it. Fragment, a part of the cache request result, can also perceive whether the user login Action cache is in fact a special case of Fragment cache, as with the session cache also has the following centralized access policy to choose from:
Memory, the quickest way, if your program only needs to run on an app server, then this is no doubt the best way file system, generally fast, but you can use regular expressions to refresh expired page Drbstore, compared to the file system, refresh the expired page faster some memcached, Faster and easier to extend than drbstore, but it does not support the use of regular refresh expiration pagesActioncontroller
Using components can have a significant impact on the performance of Actioncontroller, and my advice is that there is no particular reason not to use components, because invoking render_component causes a new request-processing loop, most of which Component can be replaced with either helper or partials.
Actionview
For each request, rails creates a controller and view instance and passes the instance variables created in the controller action through Instance_variable_get and Instance_variable_ Set is passed to view, so do not create an instance variable in the view that is not used in the
Helper Optimization
The first is pluralize, you can look at the implementation of pluralize, if not give the last argument, it will create a inflector instance, so do not write Pluralize (n, ' post '), should be written as Pluralize (n, ' post ', ' Posts ')

Followed by Link_to and Url_for, because of the need to find routing strategy, so link_to and url_for can be said to be the slowest helper method, there is no special need, do not use these two functions.
<a href= "/recipe/edit/<%=#{recipe.id}%>" class= "Edit_link" >
Look at this for something interesting
</a>
will be much faster than the following paragraph:
<%= link to "Look here for something interesting",
{: Controller => "Recipe",: Action => Edit,: ID => @recipe. ID},
{: Class => "Edit Link"}%>
ActiveRecord
Accessing an AR object's associated object is relatively slow, and you can use: include to get the associated object in advance
Class Article
Belongs To:author
End
Article. Find (: All,: include =>: Author)
Or use piggy backing to specify some fields of the associated object to get, please see this article for an introduction to piggy backing
Class Article
Piggy Back:author name: from =>: Author,: Attributes => [: Name]
End
Article = article. Find (: All,:p Iggy =>: Author)
Puts article. Author name
Also note that the field values obtained from the database are generally string types, so each access may require a type conversion, and if you need to make multiple conversions during a request processing, it is best to cache the converted values.
Also, according to my analysis of an application, about 30% of the time spent on character processing, another 30% time spent on garbage collection, and 10% for URL recognition, so caching formatted fields in the database can greatly reduce the cost of character processing.
This is the first point to get here, more optimization techniques, please pay attention to the following articles. Skyover at 2007-7-02 10:50:30 Our optimization strategy is primarily for the rails framework, and we focus on the Ruby language itself.
First, the various elements in the Ruby language, because of the different algorithms, access time is also not equal, for example, local variables in the array index, when parsing to ask the top, so the access cost is always O (1), and instance variables and method calls because of the use of hash access, so can only maintain a theoretical O (1) Access, That is, there is no conflict, and if you cannot find this method in a subclass while calling the method, you also need to retrace the search up the inheritance tree.
Therefore, you should try to avoid unnecessary polymorphism inheritance, and should try to use local variables, such as the following code is less efficient than the modified high:
def submit to remote (name, value, options = {})
options[: with] | | = ' Form.serialize (this. Form) '
Options[:html] | | = {}
Options[:html] [: type] = ' button '
Options[:html] [: onclick] = "#{remote function (options)}; return false; ”
Options[:html] [: name] = Name
options[:html [: value] = value
Tag ("Input", options[:html], false)
End
After modification:
def submit to remote (name, value, options = {})
options[: with] | | = ' Form.serialize (this. Form) '
html = (Options[:html] | | = {})
Html[:type] = ' button '
html[: onclick] = "#{remote function (Options)};" return false; ”
Html[:name] = Name
Html[:value] = value
Tag ("Input", HTML, FALSE)
End
Second, for frequently used data, should be cached, to avoid each time the use of the calculation, such as:
def capital_letters
("A"). "Z"). to a
End
It would be better to write the following:
DEF Capital Letters
@capital Letters | | = ("A".. "Z"). to a
End
Of course, for the above case, if all the classes need the same data, then it can be defined as class-level variables:
@ @capital letters = ("A"). "Z"). to a
DEF Capital Letters
@ @capital Letters
End
Of course, in addition to efficiency also pay attention to beautiful, the following code is not beautiful:
def actions
Unless @actions
# do something complicated and costly to determine action ' s value
@actions = Expr
End
@actions
End
It would be better to change this:
def actions
@actions | | =
Begin
# do something complicated and costly to determine action ' s value
Expr
End
End
In addition, there is a certain increase in the efficiency of using constants.
def validate_find_options (Options)
Options.assert valid keys (: Conditions,: Include,: joins,: Limit,: Offset,
;: o Rder,: Select,: readonly,: Group,: From)
End
The above code would be better to make the following changes:
VALID Find OPTIONS = [
;: Conditions,: Include,: joins,: Limit,
;: o Ffset,: Order,: SELECT,: readonly,: Group,: from]
def validate Find options (options)
Options.assert valid keys (valid find OPTIONS)
End
At the same time, local variables should be used as much as possible.
SQL << "GROUP by #{options[:group]}" if Options[:group]
The above method is obviously inferior to the following two kinds:
If opts = Options[:group]
SQL << "GROUP by #{opts}"
End
opts = Options[:group] and SQL << "GROUP by #{opts}"
Of course, being able to write this is the best:
SQL << "GROUP by #{opts}" if opts = Options[:group]
But syntax is not supported.
There are a few tips:
Logger.debug "args: #{hash.keys.sort.join (')}" if logger
The problem with this code is that whether or not Logger.level is Debug,hash.keys.sort.join (") is executed, so it should be written like this:
Logger.debug "args: #{hash.keys.sort.join (')}" if Logger && logger.debug?
There is also about Objectspace.each_object, in the production mode is best not to use this method.
Objectspace.each Object (Class) {|c| f (c)}
In fact, it is equal to the following code:
Objectspace.each Object {|o| o.is a? ( Class) && f (o)}
It checks every object on the heap, which can cause a great loss of performance.
OK, so much for the optimization of Ruby language, the next step is to analyze Ruby's garbage collection (GC) mechanism and give the appropriate optimization strategy. Skyover at 2007-7-02 10:51:09 We talked about how to improve our rails application performance by optimizing Ruby code, so let's take a closer look at Ruby's memory management and garbage collection mechanisms.
First, since Ruby's original design goal was to become a batch language like Perl, its memory management mechanism was not optimized for long-running service-side programs such as rails, and some even ran counter to it:
Rails ' memory management strategy is to minimize memory footprint tags and eliminate algorithms very simply using malloc to allocate contiguous blocks of memory (Ruby heap) complex data structure C extensions are easy to write, but the current C interface is difficult to implement generational GC (Ruby2) Ruby's garbage collection mechanism is not optimal for rails, because Ruby's AST (abstract syntax tree) is stored on the heap and scanned at each GC, which is exactly the largest non-spam area in rails, i.e. The GC has done most of the work of rails.
Also, Ruby's purge algorithm relies on the size of the heap, not the size of the current non-spam area, but there is a limit to the growth of the heap, and the heap will only increase if the current freelist< free_min after GC. The added value defined in GC.C is 4096, which is obviously too small for rails, and the heap should be able to hold at least 200,000 objects.
To improve the performance of Ruby GC, you can add the following statement to the rails dispatcher:
# excerpt from Dispatch. fcgi
railsfcgihandler.process! Nil, 50
This will prevent Ruby GC from running, and then enable GC after processing 50 requests, but there is a problem with this method, which cannot distinguish between small requests and large requests, which may result in:
The performance of the heap variable over size page will be compromised Ruby would still deallocate heap blocks if empty after GC (this is not very well understood, on the original text) in addition to controlling the timing of GC, you can also improve performance by modifying GC parameters, but you need to first By patching the GC, downloading the latest railsbench, Rubygc.patch patches, and then recompiling and installing Ruby, you can adjust the GC with the following parameters:
Ruby_heap_min_slots, initial heap size, default 10000 ruby HEAP free MIN,GC HEAP slot minimum, default 4096 Ruby GC MALLOC LIMIT, Allows the maximum value (in bytes) of the C data structure allocated without triggering the GC, and the default 8,000,000 our recommended values are:
Ruby_heap_min_slots = 600000
Ruby_gc_malloc_limit = 60000000
Ruby_heap_free_min = 100000
If you do benchmark tests, you'll see a lot more performance.
Finally, we talk about template optimization, and for many helper methods that know the results at compile time, there is absolutely no need to parse the request every time it is processed, such as:
<%= End_form_tag%> ===> </form>
This is simply a waste of time and the link_to we mentioned earlier, so if we can determine the output of this helper when we knock the code, it's best to write the result directly.
In addition, Ryan Davis's Parsetree and Ruby2ruby can also be used to obtain the AST of the Actionview render method and to perform template Ultimate optimization:
Expand all helper methods to remove code that is not invoked to remove the variables that are not used (and partials) merge the hash substitution constants replace the resulting determined methods call the substitution symbol and then use eval to compile the new AST into the optimized render method.
OK, here, this series of articles is over, the appendix also has some performance test data and configuration information, not to enumerate, interested can download the original view.

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.