最近多背一公斤的网站由于前段时间加了太多功能,但是没有考虑性能问题,导致速度下降,内存占用剧增,apache频频重启,需要进行优化,于是花了点时间学习了下rails的优化,整理成这篇文章,希望对遇到同样问题的人有用。
Category Archives: 性能
使用rails_reviewer优化Rails数据库查询性能
最近在做的一个项目遇到严重的性能问题,主要是数据库查询过多,同事向我推荐了rails_reviewer插件,它是我的前同事(不确定我进公司时他还在不在) David Stevenson 写的一个帮助你优化SQL查询的插件,它可以帮助你定位数据库查询的瓶颈所在,从而通过编制索引已经缓存关键变量来改进性能。 rails_reviewer的安装很简单: $ script/plugin install git://github.com/dsboulder/query_reviewer.git $ rake query_reviewer:setup 现在重启服务器,你就会在屏幕的左上角看到一个小窗口,点击这个窗口,就会显示加载当前页面的所有SQL查询,并且会告诉你那些是没问题的,那些是需要优化的,如下图: 可以看到,在优化前这个请求执行了1101条SQL查询,并且大部分都是没有cache的,你可以通过rails_reviewer查看每条查询的SQL语 句以及代码的调用栈,优化主要从两方面进行,一个是提高查询性能,这个可以通过添加索引,另一个就是减少查询数量,这个可以通过缓存变量,比如: def production(name) @production_cache ||= {} @production_cache[name] ||= buildings.map{|b| b.production(name)}.sum end 下面是优化后的结果,可以看到,优化后查询数从1101变成了94,时间则从800多毫秒减到了59毫秒,大约只有原来的10分之一不到:
Xapian,Sphinx在Rails中的性能比较
Xapian 和 Sphinx都是非常棒的开源全文搜索解决方案,但是我想知道哪个性能更好一些,Xapian官方提供了与Solr的对比,但是没有和Sphinx的,所以我决定自己来测试。 这两个都是开源的解决方案,你可以使用他们来搜索任何关键字比如马,桃子或者是Getminted等等,也可以使用通配符进行更广范围的查询。 数据集 要测试首先需要准备好数据集,我的数据是大约12000条blog记录,每个记录有两个字段需要搜索:title, description,代码如下: class Blog < ActiveRecord::Base # Thinking sphinx indexes define_index do indexes :title indexes :description end # Acts_as_xapian indexes acts_as_xapian :texts => [ :title, :description ] … end 测试环境 下面是我的测试环境: Sphinx: 0.9.8 Xapian: 1.0.1 Sphinx plugin: thinking_sphinx Xapian plugin: acts_as_xapian Ruby: 1.8.6 p114 Rails: 2.1.0 Mysql: 5.0.51 OS: [...]
使用YSlow提升应用性能
YSlow出来有一阵了,今天用了下,感觉还不错,即使你对性能优化是个外行,通过YSlow,你也可以让你的应用性能轻松得到提升。 YSlow通过分析你的应用是否符合“网页加速最佳实践”中的规则来给你的应用打分,判断的标准包括:HTTP请求数应该尽可能少,尽量减少DNS查询,尽量使用GZip压缩,CSS放在顶部,JS放在底部,使用GET发送AJAX请求,完整列表参看”最佳实践“。 这是我目前在做项目的YSlow测试结果,YSlow采用A-F评分制,A最好,F最差,可以看到,这个项目的性能隐患相当严重。 要使用YSlow,你需要首先安装Firebug,目前只支持Firefox。
使用SQLite in memory提高测试代码效率
对于需要进行持续集成的大型项目来说,运行测试代码的花费就显得尤为重要,Chris Roos在这篇帖子里介绍了一种通过使用SQLite数据库来 提高测试代码运行效率的方法。 首先你需要安装SQLite3以及sqlite3-ruby gem,然后修改database.yml: test: adapter: sqlite3 database: “:memory:” 但是这样有一个问题,由于SQLite数据库保存在内存中,因此必须在每次执行单元测试之前首先生成数据库的大纲,不过不用担心,Chris Roos已经提供了一段现成的代码,将它加到你的environment.rb就可以了: def in_memory_database? ENV["RAILS_ENV"] == “test” and ActiveRecord::Base.connection.class == ActiveRecord::ConnectionAdapters::SQLite3Adapter and Rails::Configuration.new.database_configuration['test']['database'] == ‘:memory:’ end if in_memory_database? puts “creating sqlite in memory database” load “#{RAILS_ROOT}/db/schema.rb” # use db agnostic schema by default # ActiveRecord::Migrator.up(‘db/migrate’) # use migrations end 不过要注意,以上代码最好加在”# Include your application configuration below”这一行之后,因为说不准后面的配置有可能就会用到数据库。 [...]
使用ruby-prof获取rails应用的profile
ruby-prof最新的0.5版本开始支持Rails,但是文档并没有相应更新,这是来在Charlie Savage的一篇关于如何在Rails中使用ruby-prof的指南,希望对你有所帮助。 假设你发现你的Rails应用对某个请求的处理花费了太多的时间,并且你已经排除了网络,Web Server,客户端浏览器或者是数据库的嫌疑,那么ruby-prof可以帮你轻松定位出到底是那段代码消耗了太多的时间: gem install ruby-prof windows平台用户应该选择针对win32平台的预编译版本,安装完之后,你需要从ruby-prof的安装目录下拷贝plugin到你的Rails应用的vendor/plugins目录: cd /your/ruby/gems/path/ruby-prof-0.5.0/lib/ cp ruby-prof/rails_plugin/ruby_prof rails_app/vendor/plugins 然后在你的environment.rb中增加: config.cache_classes = true 如果不设置这一项的话,那么Rails会在处理每个请求时重新将所有的model和controller都加载一遍,这会消耗大量的时间,从而使你的action的花费看起来微不足道。 完成以上操作以后,如果现在你通过浏览器向Rails发送一个请求,那么在请求处理完成后,这个请求的profile已经被写入到了Log里面,它看起来是这个样子: Completed in 0.71900 (1 reqs/sec) Rendering: 0.37600 (52%) | DB: 0.18700 (26%) 200 OK [http://localhost/] Thread ID: 175635930 Total: 0.719 %self total self wait child calls name 26.15 0.19 0.19 0.00 0.00 23 PGconn#exec 26.01 0.19 [...]
Rails应用性能优化4
上一讲我们讲解了如何通过优化Ruby代码来提升我们的Rails应用性能,这一讲,让我们更深入一些,先看看Ruby的内存管理和垃圾回收机制。 首先,由于Ruby最初的设计目标是成为像Perl那样的批处理语言,因此它的内存管理机制并没有针对Rails这样的需要长期运行的服务端程序进行最优化,有些地方甚至是背道而驰: Rails的内存管理策略是尽量减少内存占用 标记和清除算法十分简单 使用malloc来分配连续的内存块(Ruby heap) 复杂的数据结构 C扩展十分容易编写,但是当前的C接口很难实现generational GC(Ruby2有可能) 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(这句不是很理解,上原文) 除了控制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 我们的推荐值为: RUBY_HEAP_MIN_SLOTS = 600000 RUBY_GC_MALLOC_LIMIT [...]
Rails应用性能优化3
上一讲,我们的优化策略主要是针对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 ][ nclick ] = ”#{remote function(options)}; return false ; ” options[:html ][ :name] = name options[:html ][ :value] = value tag(”input” , options[:html ], false ) [...]
Rails应用性能优化2
在开始之前,有一点需要说明,优化策略事实上大部分情况下都不具备通用性,因为软硬件差异,用户使用习惯等等原因,可能会造成同一条优化策略在不同系统中 得到完全不同的效果,当然这里讲的都是一些具有普遍适用性的策略,但我还是建议你在应用这些策略时进行一下对比测试,就像第一讲开头所说,不要盲目优化。 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自动清除。 Cache优化 Rails默认支持一下集中Cache方式: Pages,很快,整个页面都被保存在文件系统,Web Server可以直接绕过APP Server完成请求应答,但是存在一些固有缺陷,比如无法应付需要用户登录的应用 Actions,第二快,缓存controller的action执行结果,同时由于可以调用到controller的过滤器,因此可以很好的防止未授权用户访问。 Fragment,缓存请求结果的一部分,也可以感知用户是否登录 Action Cache事实上是Fragment Cache的一种特殊情况,跟session一样cache也有以下集中存取策略可供选择: 内存,最快的方式,如果你的程序只需要在一个APP Server上运行,那么这无疑是最好的方式 文件系统,一般快,但可以使用正则表达式来刷新过期页面 DrbStore,同文件系统相比,刷新过期页面更为快一些 memcached,比DrbStore更快且易于扩展,但不支持使用正则刷新过期页面 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 [...]
Rails应用性能优化1
是否觉得你的Rails应用响应速度过于缓慢呢?这是RailsConf2006上的一篇关于Rails应用性能优化的演讲稿,希望能够对你有所帮助。 在优化你的应用之前,我们首先需要明确以下几点: 不先进行性能测试就盲目的优化是非常愚蠢的 如果你的应用是因为设计不合理而导致性能低下,那么我建议你最好花点时间重构你的代码,而不是进行局部的优化,这只会使问题越来越多。 在优化之前,最好先为自己树立一个目标,这样可以防止因为过度优化而浪费时间,达到预期的目标后就该适可而止 没有必要对每一个页面都进行优化,只需要关注那些最经常被访问的页面就可以了, 在开发期间,进行持续的性能测量,这样有助于你在优化时定位性能瓶颈。 在优化完成后,要评估我们优化的质量,我们就需要先确定一组性能参数: 延迟,响应一个请求需要多少时间 吞吐量,每秒最多可以处理多少个请求 系统利用率,在大量请求需要处理的时候,你的系统在满负荷运转吗? 资源开销,在每个请求上所花费的开销 确定了要测量的性能参数,我们需要自动化的基准(benchmark)工具来帮我们进行优化前后的性能对比: Rails日志文件(debug_level >= Logger::DEBUG) Rails日志分析工具(需要将日志输出到syslog) Rails基准脚本(script/benchmarker) 数据库提供的性能分析器 Apache Bench(ab或者ab2) httperf railsbench 可在http://rubyforge.org/projects/railsbench/下载 我推荐Railsbench,它可以测量Rails处理一个请求的原始性能,关于Railsbench后面的文章会有介绍。 除了基准测试工具,你也可以选择单纯的性能测试工具: Ruby profiler Zen profiler rubyprof Rails profiler script Ruby Performance Validator(商业软件,仅支持windows) 不过事实上,Railsbench已经内置了性能测试工具,所以单独使用这些工具的必要性不大。 工具已经搞定,下面就让我们开始我们的优化之旅吧! 根据我的经验,Rails性能问题一般集中在以下几个方面: 很慢的helper方法 负责的路由 过多的联合(associations) 过多访问数据库 缓慢的session存取 不过,数据库的性能基本可以不用考虑,因为连接数据库的主要开销事实上在于建立ActiveRecord对象。 这一讲就到这里,下一讲我们将针对以上几个问题给出具体的优化方案。
