我一直在阅读有关减少Ruby/Rails应用程序中内存使用的方法,其中提到的一件事是冻结对象。我已经尝试了下面的代码(MRI,Ruby 2.3.3(,根据活动监视器,与不冻结字符串相比,它确实节省了内存。
pipeline = []
100_000.times { pipeline << 'hello world'.freeze }
但是,如果我尝试使用哈希文字进行相同的操作,它会占用大量内存,除非我将哈希分配给变量并冻结它。
pipeline = []
100_000.times { pipeline << {hello: 'world'}.freeze } # Uses about 25MB
my_hash = {hello: 'world'}
my_hash.freeze
100_000.times { pipeline << my_hash} # This uses about 1MB
谁能解释为什么?我一直认为字符串大小写有点奇怪,因为它看起来你只是在创建许多不同的字符串对象,分别冻结每个对象,并向数组中添加大量冻结对象。不知道它为什么有效,但嘿,它确实如此。现在,哈希大小写更符合我的预期,但我不知道为什么它的行为不像字符串。
Ruby 优化器可能可以将该字符串从一个循环识别为相同,但它无法将该哈希识别为相同,因此它会生成新的哈希。在第二个变体中,您实际上使用相同的哈希,以便优化器可以处理它。
为了证明这一点,请看这个:
pipeline = []
100_000.times { pipeline << 'hello world'.freeze }
pipeline.map(&:object_id).uniq.length
# => 1
这是一个相同对象的数组,只有一个分配。
pipeline = []
100_000.times { pipeline << {hello: 'world'}.freeze }
pipeline.map(&:object_id).uniq.length
# => 100000
这是 100,000 个不同的对象。
谁能解释为什么?我一直认为字符串大小写有点奇怪,因为它看起来你只是在创建许多不同的字符串对象,分别冻结每个对象,并向数组中添加大量冻结对象。
表达式形式
'string literal'.freeze
是语言特殊情况的特殊表达形式。它不仅冻结字符串对象,还执行重复数据消除。(类似于符号。
它是一种特殊大小写的表达式形式。它不是计算字符串文字,然后将消息发送到freeze
。相反,它被视为单个实体,如果您愿意,可以将其视为不同形式的字符串文本。
事实上,最初的提案确实引入了一种不同形式的字符串文字,如下所示:
'string literal'f
该提案被更改以使其向前兼容:如果您必须在旧版本的 Ruby 中运行代码,而'foo'.freeze
在旧版本的 Ruby 中的工作方式相同,'foo'f
将是一个语法错误,它只使用更多的内存。
注意:这意味着它仅适用于文字。在这里,字符串被消除重复:
'foo'.freeze
在这里,它不是:
foo = 'foo'
foo.freeze
不知道它为什么有效,但嘿,它确实如此。
基本上,它是有效的,因为语言规范是这样说的。
现在,哈希大小写更符合我的预期,但我不知道为什么它的行为不像字符串。
同样,它不起作用,因为语言规范仅处理特殊情况的字符串文字。