最近我发现Ruby并没有优化[]
和{}
来指向一个公共的共享对象。演示:
irb(main):001:0> [].object_id
=> 70284401361960
irb(main):002:0> [].object_id
=> 70284392762340 # different
irb(main):003:0> [].object_id
=> 70284124310100 # different
irb(main):005:0> {}.object_id
=> 70284392857480
irb(main):006:0> {}.object_id
=> 70284392870480 # different
irb(main):007:0> {}.object_id
=> 70284392904360 # different
我知道通常使用空散列和数组字面量来初始化将立即发生变化的值。但是,即使您使用[].freeze.object_id
或{}.freeze.object_id
,也会发生这种情况。
与String
相比,当env变量RUBYOPT
被设置为--enable-frozen-string-literal
时:
irb(main):001:0> ""
=> 70284400947400
irb(main):002:0> ""
=> 70284400947400 # same
irb(main):003:0> ""
=> 70284400947400 # same
即使您不启用冻结字符串字面值,如果您调用"".freeze.object_id
,您每次都会获得相同的对象id,尽管我怀疑初始""
字面值仍然分配了一个中间字符串对象,freeze
正在被调用。
在一个性能敏感的代码库(好吧,作为性能敏感的你可以允许自己仍然使用MRI lol),我已经看到了这个解决方案,它使用以下共享常量而不是[]
或{}
的情况下,哈希或数组不需要是可变的:
module LessAllocations
EMPTY_HASH = {}.freeze
EMPTY_ARRAY = [].freeze
# String literals are already frozen, use '' instead
# EMPTY_STRING = ''
end
我的问题是:
这是错过的优化机会吗?
似乎可以写一个窥视孔优化来实习冻结的空散列或数组字面量。将需要成为语言的语义的一部分,即是否有任何可观察到的差异,(显然除了
object_id
的行为)?空散列和数组字面量由标记指针表示吗?它们会导致分配吗?
这是错过的优化机会吗?
要回答这个问题,必须对现实世界的内存使用情况进行调查,但我认为这不太可能。你可以做个小实验…
class Array
EMPTY_ARRAY = [].freeze
def freeze
empty? ? EMPTY_ARRAY : super
end
end
空对象非常小。与你的程序正在使用内存的其他事情相比,它们占用了大量的内存,这是一种边缘情况。
我理解通常使用空散列和数组字面量来初始化将立即发生变化的值。
因此,对空散列和数组添加写时复制可能会减慢速度。
但是即使您执行[].freeze.object_id或{} .freeze.object_id。
冷冻意味着你提前知道它们将保持空状态,这是非常罕见的。有这么多已知的空散列和数组,这会成为性能问题,这是一种极端情况。不断的变通似乎很好。