如何提高 Ruby On Rails 性能(2)

update books
  set author = 'David'
  where title LIKE '%Rails%'
Another example is iteration over a large dataset. Sometimes you need only the data. No typecasting, no updates. This snippet just runs the query and avoids ActiveRecord altogether:
result = ActiveRecord::Base.execute 'select * from books'
result.each do |row|
  # do something with row.values_at('col1', 'col2')
end

2.1.3 字符串回调

Rails 回调像之前/之后的保存,之前/之后的动作,以及大量的使用。但是你写的这种方式可能影响你的性能。这里有 3 种方式你可以写,比如:在保存之前回调:
 
before_save :update_status<br>before_save do |model|
  model.update_status<br>end<br>before_save "self.update_status"
 

前两种方式能够很好的运行,但是第三种不可以。为什么呢?因为执行 Rails 回调需要存储执行上下文(变量,常量,全局实例等等)就是在回调的时候。如果你的应用很大,你最终在内存里复制了大量的数据。因为回调在任何时候都可以执行,内存在你程序结束之前不可以回收。

有象征,回调在每个请求为我节省了 0.6 秒。

2.2 写更少的 Ruby

这是我最喜欢的一步。我的大学计算机科学类教授喜欢说,最好的代码是不存在的。有时候做好手头的任务需要其它的工具。最常用的是数据库。为什么呢?因为 Ruby 不善于处理大数据集。非常非常的糟糕。记住,Ruby 占用非常大的内存。所以举个例子,处理 1G 的数据你可能需要 3G 的或者更多的内存。它将要花费几十秒的时间去垃圾回收这 3G。好的数据库可以一秒处理这些数据。让我来举一些例子。

2.2.1 属性预加载

有时候反规范化模型的属性从另外一个数据库获取。比如,想象我们正在构建一个 TODO 列表,包括任务。每个任务可以有一个或者几个标签标记。规范化数据模型是这样的:

Tasks
 id
 name
Tags
 id
 name
Tasks_Tags
 tag_id
 task_id
 

加载任务以及它们的 Rails 标签,你会这样做:

tasks = Task.find(:all, :include => :tags)
    > 0.058 sec
 

这段代码有问题,它为每个标签创建了对象,花费很多内存。可选择的解决方案,将标签在数据库预加载。

tasks = Task.select <<-END
      *,
      array(
        select tags.name from tags inner join tasks_tags on (tags.id = tasks_tags.tag_id)
        where tasks_tags.task_id=tasks.id
      ) as tag_names
    END
    > 0.018 sec
 

这只需要内存存储额外一列,有一个数组标签。难怪快 3 倍。

2.2.2 数据集合

我所说的数据集合任何代码去总结或者分析数据。这些操作可以简单的总结,或者一些更复杂的。以小组排名为例。假设我们有一个员工,部门,工资的数据集,我们要计算员工的工资在一个部门的排名。

SELECT * FROM empsalary;
  depname  | empno | salary
-----------+-------+-------
 develop  |    6 |  6000
 develop  |    7 |  4500
 develop  |    5 |  4200
 personnel |    2 |  3900
 personnel |    4 |  3500
 sales    |    1 |  5000
 sales    |    3 |  4800
 

你可以用 Ruby 计算排名:

salaries = Empsalary.all
salaries.sort_by! { |s| [s.depname, s.salary] }
key, counter = nil, nil
salaries.each do |s|
 if s.depname != key
  key, counter = s.depname, 0
 end
 counter += 1
 s.rank = counter
end

Empsalary 表里 100K 的数据程序在 4.02 秒内完成。替代 Postgres 查询,使用 window 函数做同样的工作在 1.1 秒内超过 4 倍。

SELECT depname, empno, salary, rank()
OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
  depname  | empno | salary | rank
-----------+-------+--------+------
 develop  |    6 |  6000 |    1
 develop  |    7 |  4500 |    2
 develop  |    5 |  4200 |    3
 personnel |    2 |  3900 |    1
 personnel |    4 |  3500 |    2
 sales    |    1 |  5000 |    1
 sales    |    3 |  4800 |    2

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/15840.html