Rails应用性能优化

转载 2007年09月25日 22:47:00
是否觉得你的Rails应用响应速度过于缓慢呢?这是RailsConf2006上的一篇关于Rails应用性能优化的演讲稿,希望能够对你有所帮助。
在优化你的应用之前,我们首先需要明确以下几点:
  •      不先进行性能测试就盲目的优化是非常愚蠢的
  •      如果你的应用是因为设计不合理而导致性能低下,那么我建议你最好花点时间重构你的代码,而不是进行局部的优化,这只会使问题越来越多。
  •      在优化之前,最好先为自己树立一个目标,这样可以防止因为过度优化而浪费时间,达到预期的目标后就该适可而止
  •      没有必要对每一个页面都进行优化,只需要关注那些最经常被访问的页面就可以了,
  •      在开发期间,进行持续的性能测量,这样有助于你在优化时定位性能瓶颈。
  1.  
在优化完成后,要评估我们优化的质量,我们就需要先确定一组性能参数:
  •      延迟,响应一个请求需要多少时间
  •      吞吐量,每秒最多可以处理多少个请求
  •      系统利用率,在大量请求需要处理的时候,你的系统在满负荷运转吗?
  •      资源开销,在每个请求上所花费的开销
  1.  
确定了要测量的性能参数,我们需要自动化的基准(benchmark)工具来帮我们进行优化前后的性能对比:
  •      Rails日志文件(debug_level >= Logger::DEBUG)
  •      Rails日志分析工具(需要将日志输出到syslog)
  •      Rails基准脚本(script/benchmarker)
  •      数据库提供的性能分析器
  •      Apache Bench(ab或者ab2)
  •      httperf
  •      railsbench
    1.  
  1.  
我推荐Railsbench,它可以测量Rails处理一个请求的原始性能,关于Railsbench后面的文章会有介绍。
除了基准测试工具,你也可以选择单纯的性能测试工具:
  •      Ruby profiler
  •      Zen profiler
  •      rubyprof
  •      Rails profiler script
  •      Ruby Performance Validator(商业软件,仅支持windows)
  1.  
不过事实上,Railsbench已经内置了性能测试工具,所以单独使用这些工具的必要性不大。
工具已经搞定,下面就让我们开始我们的优化之旅吧!
根据我的经验,Rails性能问题一般集中在以下几个方面:
  •      很慢的helper方法
  •      负责的路由
  • 过多的联合(associations)
  •      过多访问数据库
  •      缓慢的session存取
  1.  

不过,数据库的性能基本可以不用考虑,因为连接数据库的主要开销事实上在于建立ActiveRecord对象。
这一讲就到这里,下一讲我们将针对以上几个问题给出具体的优化方案。

 

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

 

在开始之前,有一点需要说明,优化策略事实上大部分情况下都不具备通用性,因为软硬件差异,用户使用习惯等等原因,可能会造成同一条优化策略在不同系统中得到完全不同的效果,当然这里讲的都是一些具有普遍适用性的策略,但我还是建议你在应用这些策略时进行一下对比测试,就像第一讲开头所说,不要盲目优化。
Session优化
如果你的系统需要为每个访问者保存单独的Session信息(比如购物网站),那么session的存取速度将是影响系统性能的关键因素,目前可用的session存取策略有:
  • 内存,快,相当快!但是如果你的应用挂了,或者由于其它什么原因需要重启,那么所有的session信息都会丢失,并且这种方式仅仅只能在单APP Server的应用中使用
  • 文件系统,很容易使用,每个session对应一个文件,并且可以通过NFS或者NAS轻松进行容量扩展,但是速度较慢
  • 数据库/ActiveRecordStore,使用简单(Rails的默认策略),但是很慢
  • 数据库/SQLSessionStore,与上面一种方式类似,但是使用原始SQL取代了ActiveRecord,性能有一定提升,关于SQLSessionStore与ActiveRecordStore的对比可以参看这篇文章
  • memcached,比SQLSessionStore稍微快一些,可扩展性较好,但是较难获取统计信息,关于memcached与SQLSessionStore的对比,请参看这篇文章
  • DrbStore,在memcached不支持的一些平台上,可以选择DrbStore,但是性能比memcached要差一些,并且不支持session自动清除。
  1.  
Cache优化
Rails默认支持一下集中Cache方式:
  • Pages,很快,整个页面都被保存在文件系统,Web Server可以直接绕过APP Server完成请求应答,但是存在一些固有缺陷,比如无法应付需要用户登录的应用
  • Actions,第二快,缓存controller的action执行结果,同时由于可以调用到controller的过滤器,因此可以很好的防止未授权用户访问。
  • Fragment,缓存请求结果的一部分,也可以感知用户是否登录
  1.  
Action Cache事实上是Fragment Cache的一种特殊情况,跟session一样cache也有以下集中存取策略可供选择:
  • 内存,最快的方式,如果你的程序只需要在一个APP Server上运行,那么这无疑是最好的方式
  • 文件系统,一般快,但可以使用正则表达式来刷新过期页面
  • DrbStore,同文件系统相比,刷新过期页面更为快一些
  • memcached,比DrbStore更快且易于扩展,但不支持使用正则刷新过期页面
  1.  
ActionController
使用Components会对ActionController的性能造成较大的影响,我的建议是没有特别的理由,不要使用components,因为调用render_component会引发一个新的请求处理循环,大部分情况下,component都可以使用helper或者partials代替。
ActionView
对于每一个请求,Rails都会创建一个controller和view实例,并会将controller的action中创建的实例变量通过instance_variable_get和instance_variable_set传递给view,因此不要在action中创建view中用不到的实例变量
helper优化
首先是pluralize,可以看一下pluralize的实现,如果不给出最后一个参数,它会创建一个Inflector实例,因此不要写pluralize(n, ‘post’),应该写成pluralize(n, ‘post’, ‘posts’)

其次是link_to与url_for,由于需要查找路由策略,因此link_to与url_for可以说是最慢的helper方法,没有特别的需要,不要使用这两个函数。
<a href="/recipe/edit/<%=#{recipe.id}%>" class="edit_link">
look here for something interesting
</a>
会比下面这段快许多:
<%= link to ”look here for something interesting” ,
{ :controller => ”recipe”, :action => edit, :id => @recipe.id },
{ :class => ” edit link ” } %>
ActiveRecord
访问AR对象的关联对象相对而言会比较慢,可以使用:include提前获取关联对象
class Article
  belongs to :author
end
Article . find ( :all , :include => :author)
或者使用piggy backing指定要获取的关联对象的某些字段,关于piggy backing的介绍请参看这篇文章
class Article
  piggy back :author name, :from => :author, :attributes => [:name]
end
article = Article . find ( :all , :piggy => :author)
puts article .author name
另外需要注意的是,从数据库中获取的字段值一般来说都是String类型,因此每次访问可能都需要进行类型转换,如果你在一个请求处理过程中需要进行多次转换,那么最好对转换后的值进行缓存。
还有,根据我对一个应用的分析,大约有30%的时间花在了字符处理上,另外30%时间花在了垃圾收集,还有10%用于URL识别,因此在数据库中缓存格式化后的字段可以大大减小字符处理的开销。
这一讲就先到这里,更多的优化技巧,请关注后续文章。
skyover at 2007-7-02 10:50:30
上一讲,我们的优化策略主要是针对Rails框架进行,这一讲,我们将精力集中到Ruby语言本身。
首先,Ruby语言中的各种元素由于算法的不同,访问时间也各不相等,比如局部变量采用数组索引,在解析时进行顶问,因此访问代价总是O(1),而实例变量和和方法调用由于使用Hash访问,因此只能保持理论上的O(1)访问,也就是没有冲突的情况下,同时调用方法时如果不能在子类找到这个方法,则还需要沿继承树向上回溯查找。
因此,应该尽量避免不必要的多态继承,同时应该尽量使用局部变量,比如下面这段代码的效率就不如修改后的高:
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
修改后:
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
其次,对于经常用到的数据,应该进行缓存,避免每次用到时再进行计算,比如:
def capital_letters
  ( ”A” .. ”Z” ). to a
end
写成下面这样会更好:
def capital letters
  @capital letters ||= ( ”A” .. ”Z” ). to a
end
当然对于上面这种情况,如果所有类需要的数据都相同,那么完全可以将它定义成class级变量:
@@capital letters = (”A” .. ”Z” ). to a
def capital letters
  @@capital letters
end
当然,除了效率也要注意优美,下面这段代码就不够优美:
def actions
  unless @actions
    # do something complicated and costly to determine action’s value
    @actions = expr
  end
  @actions
end
改成这样会更好一些:
def actions
  @actions ||=
  begin
    # do something complicated and costly to determine action’s value
    expr
  end
end
另外,使用常量对效率也有一定提升。
def validate_find_options (options)
  options.assert valid keys( :conditions , :include , :joins , :limit , :offset ,
          ;:order , :select , :readonly, :group, :from )
end
上面这段代码进行如下修改会更好一些:
VALID FIND OPTIONS = [
    ;:conditions , :include , :joins , :limit ,
    ;:offset , :order , :select , :readonly, :group, :from ]
def validate find options (options)
  options.assert valid keys(VALID FIND OPTIONS)
end
同时,应该尽可能的使用局部变量。
sql << ” GROUP BY #{options[:group]} ” if options[:group]
上面这种方式明显不如以下两种:
if opts = options[:group]
  sql << ” GROUP BY #{opts} ”
end
opts = options[:group] and sql << ” GROUP BY #{opts} ”
当然,能够写成这样是最好的:
sql << ” GROUP BY #{opts} ” if opts = options[:group]
但是语法不支持。
还有一些小技巧:
logger.debug ”args: #{hash.keys.sort.join ( ’ ’ )}” if logger
这段代码的问题在于,不管logger.level是否为DEBUG,hash.keys.sort.join(’ ’) 都会被执行,因此,应该写成这样:
logger.debug ”args: #{hash.keys.sort.join ( ’ ’ )}” if logger && logger.debug?
还有就是关于ObjectSpace.each_object的,在production模式最好不要使用这个方法。
ObjectSpace.each object(Class) {|c| f(c) }
事实上跟下面的代码是相等的:
ObjectSpace.each object {|o| o.is a?(Class) && f(o) }
它会对堆上的每一个对象都进行检查,这会对性能造成极大损耗。
好了,关于Ruby语言的优化技巧就这么多了,下一讲我们将对Ruby的垃圾回收(GC)机制进行分析,并给出相应的优化策略。
skyover at 2007-7-02 10:51:09
上一讲我们讲解了如何通过优化Ruby代码来提升我们的Rails应用性能,这一讲,让我们更深入一些,先看看Ruby的内存管理和垃圾回收机制。
首先,由于Ruby最初的设计目标是成为像Perl那样的批处理语言,因此它的内存管理机制并没有针对Rails这样的需要长期运行的服务端程序进行最优化,有些地方甚至是背道而驰:
  • Rails的内存管理策略是尽量减少内存占用
  • 标记和清除算法十分简单
  • 使用malloc来分配连续的内存块(Ruby heap)
  • 复杂的数据结构
  • C扩展十分容易编写,但是当前的C接口很难实现generational GC(Ruby2有可能)
  1.  
Ruby的垃圾回收机制对于Rails也不是最优的,由于Ruby的AST(抽象语法树)存储在堆上,并且在每次GC时都会被扫描一遍,而这恰恰是Rails中最大的一块非垃圾区,也就是说,GC对于Rails做的大部分工作都是做无用功。
并且,Ruby的清除算法依赖于堆的大小,而不是当前非垃圾区的大小,但是堆的增长存在一定限制,只有当进行GC后,当前的freelist< FREE_MIN,堆才会增加,gc.c中定义的增加值为4096,这对于Rails来说明显太小了,堆应该至少能够容纳20万个对象。
要提高Ruby GC的性能,可以在Rails dispatcher中添加如下语句:
# excerpt from dispatch. fcgi
RailsFCGIHandler.process! nil, 50
这句话将禁止Ruby GC运行,在处理50个请求后再启用GC,但是这个方法存在一个问题,它没法区分小请求和大请求,这有可能会导致:
  • 堆变的过大
  • 小页面的性能会受损
  • Ruby will still deallocate heap blocks if empty after GC(这句不是很理解,上原文)
  1.  
除了控制GC的运行时机,也可以通过修改GC的参数来提升性能,但需要先给GC打补丁,下载最新的railsbench,打上rubygc.patch补丁,然后重新编译并安装Ruby,就可以通过以下参数对GC进行调整了:
  • RUBY_HEAP_MIN_SLOTS, 初始堆大小,默认10000
  • RUBY HEAP FREE MIN,GC后可用的heap slot的最小值,默认4096
  • RUBY GC MALLOC LIMIT,允许不触发GC而分配的C数据结构的最大值(字节为单位),默认8,000,000
  1.  
我们的推荐值为:
RUBY_HEAP_MIN_SLOTS = 600000
RUBY_GC_MALLOC_LIMIT = 60000000
RUBY_HEAP_FREE_MIN = 100000
如果你进行基准测试的话,就会发现性能提高不少。
最后,我们再讲讲模板优化,对于许多在编译时就知道结果的helper方法,完全没有必要在每次处理请求时都进行解析,比如:
<%= end_form_tag %> ===> </form>
这纯粹就是浪费时间,还有我们前面提到的link_to,因此,如果我们可以在敲代码时确定这个helper的输出,那么最好直接写出结果。
另外,还可以使用Ryan Davis的ParseTree和ruby2ruby来获取ActionView的render方法的AST,并进行模板终极优化:
  • 展开所有helper方法
  • 去除不会调用到的代码
  • 去除不会用到的变量(以及partials)
  • 合并hash
  • 替换常量
  • 替换结果已确定的方法调用
  • 替换符号
  1.  
然后使用eval将新的AST编译入优化后的render方法。
好了,到这里,这一系列的文章就结束了,附录中还有一些性能测试的数据和配置信息,就不一一列举了,感兴趣的可以下载原文查看。

Ruby On Rails 性能提升

1 Introduction简介 大家总是说 Rails 好慢啊,这差不多已经成为 Ruby and Rails 社区里的一个老生常谈的问题了。然而实际上这个说法并不正确。只要正确使用 Rail...

LEVELDB(SSDB)关于读操作两种CACHE的作用和配置

SSDB及LEVELDB的用来优化查找Cache分为两种,分别是table_cache和block_cache。      table_cache用来缓存的是sstable的索引数据,也可以理解为m...

Rails应用性能优化一例

Rails是一款经典的Web开发框架,它的许多设计思想为使用者带来了很多开发上的便利。它非常适合于快速搭建Web应用程序,修改和维护的便利性使它受到许多创业型公司的青睐。然而过度地依赖其所带来的便利而...

怎么提高RailS应用的性能

Is rails slow? 「铁路很慢」,你也许听过这个笑话,那么我们的Rails框架呢? 如果说Rails慢,那么如何提升Rails APP的性能就成了开发者们最关注的问题。 也许你听...

九个衡量 Rails 应用性能的小方法

你有个绝佳的商业创意,日复一日地将它完善丰满起来。后来,你雇了一群天赋异禀的开发者、Web 设计师和用户体验专家,他们用一种非常棒的框架——Ruby on Rails 帮你实现长久以来的梦想。你的网站...

如何提升 RailS 应用的性能?

Is rails slow?「铁路很慢」,你也许听过这个笑话,那么我们的 Rails 框架呢? 如果说 Rails 慢,那么如何提升 Rails APP 的性能就成了开发者们最关注的问题。也许你听说...

rails性能优化

  • 2013年04月16日 18:01
  • 357KB
  • 下载

如何提高 Ruby On Rails 性能

大家总是说 Rails 好慢啊,这差不多已经成为 Ruby and Rails 社区里的一个老生常谈的问题了。然而实际上这个说法并不正确。只要正确使用 Rails,把你的应用运行速度提升 10 倍并不...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Rails应用性能优化
举报原因:
原因补充:

(最多只允许输入30个字)