How Ruby Uses Memory_Ruby_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > Ruby > How Ruby Uses Memory

How Ruby Uses Memory

 2016/5/12 5:32:16  michael_roshen  程序员俱乐部  我要评论(0)
  • 摘要:从来没有见过哪个开发者抱怨代码运行太快,或者内存使用太少。在Ruby里,内存的使用也非常重要,只有很少一部分人知道它们的程序是如何引起内存占用过多或减少的,本文将首先对Ruby对象与内存的联系做一个基本的介绍,使用一些技巧来用更少的内存加速程序运行速度。ObjectRetention显然,占用内存过多一个最直接的原因就是内存中保存的对象太多,在Ruby中,如果一个常量关联到一个对象,那么它不会被垃圾回收器(GC)回收。RETAINED=[]100_000.timesdoRETAINED<
  • 标签:Ruby



从来没有见过哪个开发者抱怨代码运行太快,或者内存使用太少。 在Ruby里,内存的使用也非常重要, 只有很少一部分人知道它们的程序是如何引起内存占用过多或减少的,本文将首先对Ruby对象与内存的联系做一个基本的介绍,使用一些技巧来用更少的内存加速程序运行速度。

Object Retention

显然,占用内存过多一个最直接的原因就是内存中保存的对象太多, 在Ruby中,如果一个常量关联到一个对象,那么它不会被垃圾回收器(GC)回收。

RETAINED = []
100_000.times do
? RETAINED << "a string"
end
我们来做个测试,GC.stat(:total_freed_objects) 这个方法返回的是GC释放掉的对象数量,在上段代码的前后我们来看一下对象的释放数量

require 'get_process_mem'
GC.start
before = GC.stat(:total_freed_objects)
puts mem = GetProcessMem.new.kb
RETAINED = []
100_000.times do
? RETAINED << "a string"
end
puts mem = GetProcessMem.new.kb
GC.start
after = GC.stat(:total_freed_objects)
puts "Objects Freed: #{after - before}"

# 7925.0kb
# 12497.5kb
# Objects Freed: 4315

创建了100000个对象,只释放了4315个,很明显,这些对象并不是RETAINED中的对象,而且内存占用也明显上升了很多。这说明这些对象并没有被GC回收,
全局的引用对象不会被GC回收,比如:常量,全局变量,类,模块。所以在使用这些对象的时候要非常小心。

如果我们不保留任何对象,做同样的操作会怎么样呢?

require 'get_process_mem'
GC.start
before = GC.stat(:total_freed_objects)
puts mem = GetProcessMem.new.kb
100_000.times do
? foo = "a string"
end
puts mem = GetProcessMem.new.kb
GC.start
after = GC.stat(:total_freed_objects)
puts "Objects Freed: #{after - before}"
# 7921.0kb
# 7985.0kb
# Objects Freed: 104283

我们发现内存的占用并没有发生太大的变化,因为GC回收了我们创建的100000个对象

Retention for Speed

了解Ruby的开发者都知道DRY的原则,在分配对象的时候也是一样,有时保存对象比一遍一遍的创建对象更有意义,Ruby中有这样一个方法freeze, 解释器会知道你不打算修改这个字符串,它将来会被重用。

require 'get_process_mem'
GC.start
before = GC.stat(:total_freed_objects)
puts mem = GetProcessMem.new.kb
RETAINED = []
100_000.times do
? RETAINED << "a string".freeze
end
puts mem = GetProcessMem.new.kb
GC.start
after = GC.stat(:total_freed_objects)
puts "Objects Freed: #{after - before}"
7913.0
8849.5
Objects Freed: 4315

与常量的例子对比一下,发现虽然没有释放对象,但是内存的使用确少了很多。正因为常量并不会被GC回收,通常我们会使用它来存储外部连接,比如Redis

RETAINED_REDIS_CONNECTION = Redis.new

Short Lived Objects

很多对象的生命周期很短,创建成功并且没有引用后很快就被回收掉了,看看这段代码:

User.where(name: "schneems").first

看上去只需要几个对象而已,一个:name 符号,一个"schneems"字符串,但是,当我们调用它的时候,很多很多的对象就会被创建出来用于生成正确的sql语句,这些对象中的大多数都不会持续的太久,如果它们不会被保留,那么我们为什么还要关心呢?

随着时间的推移,这些存活时间比较长的对象会导致内存使用率上升,GC会需要更多的内存来清理这些对象。

Ruby Memory Goes Up

当我们需要更多的对象,以至于超过Ruby可提供的内存时,它就会像操作系统申请新的内存空间, 但是向系统申请内存这个操作是非常昂贵的,所以Ruby尽量减少这种操作,通常可以设置环境变量RUBY_GC_HEAP_GROWTH_FACTOR, 这个变量的意义是:如果默认Ruby的内存是100mb,设置RUBY_GC_HEAP_GROWTH_FACTOR=1.1,那么当Ruby向操作系统申请内存的时候,会申请110mb, 它会按比例继续增加。值越小说明我们会越频繁的进行GC和内存分配。如果设置的过大,那么实际得到的内存会超过我们所需要的,所以这个值要根据实际情况进行调整。

def make_an_array
? array = []
? 10_000_000.times do
??? array <<? "a string"
? end
? return nil
end

当我们执行上面方法的时候,他会生成10000000个字符串对象,当方法退出的时候哦,这些对象会被GC, 但是当程序执行的时候,Ruby必须要分配新的内存来存放这些对象,因为这些对象需要至少500mb的内存!内存会慢慢的,逐渐的被释放,如果你关心性能,那么最好尽可能的减少对象的创建。

我们来看一下 mime-types 这个gem中的代码:

matchdata.captures.map { |e|
? e.downcase.gsub(%r{[Xx]-}o, '')
end

在block中将字符串转换成小写,并根据正则表达式替换到一些字符串,看上去没有什么问题,但是当它被调用上千次的时候, 每次调用downcase gsub都会创建一个新的对象,这即浪费时间,又浪费内存,为了避免这种情况,我们可以这样写:

matchdata.captures.map { |e|
? e.downcase!
? e.gsub!(%r{[Xx]-}o, ''.freeze)
? e
}

本例中,对原始数据的修改对我们没有影响,所以没有必要再重新创建,这样速度就会非常快了。我们不需要定义一个常量来存储正则表达式,因为所有的正则表达式已经被Ruby解释器进行了“frozen”.

对大多数应用程序来说, 比如web程序,导致内成占用过高的操作很可能经常被触发,我们不能单靠Ruby来释放内存,最好的办法 就是尽量减少对象的创建。


更多精彩内容:



发表评论
用户名: 匿名