为什么空数组和哈希不被扣留?



最近我发现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

我的问题是:

  1. 这是错过的优化机会吗?

    似乎可以写一个窥视孔优化来实习冻结的空散列或数组字面量。将需要成为语言的语义的一部分,即是否有任何可观察到的差异,(显然除了object_id的行为)?

  2. 空散列和数组字面量由标记指针表示吗?它们会导致分配吗?

这是错过的优化机会吗?

要回答这个问题,必须对现实世界的内存使用情况进行调查,但我认为这不太可能。你可以做个小实验…

class Array
EMPTY_ARRAY = [].freeze

def freeze
empty? ? EMPTY_ARRAY : super
end
end

空对象非常小。与你的程序正在使用内存的其他事情相比,它们占用了大量的内存,这是一种边缘情况。

我理解通常使用空散列和数组字面量来初始化将立即发生变化的值。

因此,对空散列和数组添加写时复制可能会减慢速度。

但是即使您执行[].freeze.object_id或{} .freeze.object_id。

冷冻意味着你提前知道它们将保持空状态,这是非常罕见的。有这么多已知的空散列和数组,这会成为性能问题,这是一种极端情况。不断的变通似乎很好。

最新更新