【发布时间】:2021-05-29 07:06:12
【问题描述】:
最近我发现 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,也会发生这种情况。
当环境变量RUBYOPT 设置为--enable-frozen-string-literal 时,与String 对比:
irb(main):001:0> ""
=> 70284400947400
irb(main):002:0> ""
=> 70284400947400 # same
irb(main):003:0> ""
=> 70284400947400 # same
即使您不启用冻结字符串文字,如果您改为调用 "".freeze.object_id,每次都会获得相同的对象 ID,尽管我怀疑初始 "" 文字仍然分配了一个中间字符串对象 @正在调用 987654333@。
在一个对性能敏感的代码库中(好吧,作为对性能敏感的你可以让自己在仍然使用 MRI 的同时保持哈哈)我已经看到了这个解决方法,它使用以下共享常量而不是 [] 或 {}对于哈希或数组不需要可变的情况:
module LessAllocations
EMPTY_HASH = {}.freeze
EMPTY_ARRAY = [].freeze
# String literals are already frozen, use '' instead
# EMPTY_STRING = ''
end
所以我的问题是:
-
这是错失的优化机会吗?
似乎可以将窥视孔优化写入实习生冻结的空哈希或数组文字。需要成为语言语义的一部分,即是否会有任何可观察到的差异(显然除了
object_id的行为)? -
空的散列和数组字面量是否由标记指针表示?它们甚至会导致任何分配发生吗?
【问题讨论】:
-
如果
[]总是同一个对象,你能想象所有可怕的副作用吗?除非您建议Array文字应该是不可变的,否则它有一组完全不同的缺点。Hash会遇到同样的问题,而且问题更多。 -
@engineersmnky “我知道空的哈希值和数组字面量通常用于初始化将立即发生变异的值。但即使您执行 [].freeze.object_id 或 {}.freeze 也会发生这种情况。而是 object_id。”
-
[]和{}的大小实际上大约是 8 个字节。它们已经超级便宜了。除非您要处理数十亿个这样的问题,否则这里节省的费用可以忽略不计。 -
@tadman 这不仅仅是总内存大小,还有 GC 簿记开销。还要考虑您不能 malloc 小于特定系统定义大小的块。仅分配 0 或 1 个字节会产生 16 字节的分配(在我的 64 位 Intel mac 上的 macOS 上)。首先,它似乎也是more than 8 bytes。据我统计,它是 5 个字,40 个字节,在我的系统上最少分配 48 个字节
-
您需要进行一些测试,并展示修复此问题实际上会如何影响性能,如果确实如此,那么请努力寻找不会破坏任何东西的解决方案。从 GC 的角度来看,一个空数组的跟踪成本很低,它并不复杂,而且很容易处理。大型的自引用结构是 GC 变得昂贵的地方。
标签: arrays ruby optimization language-lawyer