【问题标题】:Why aren't empty arrays and hashes interned?为什么不保留空数组和哈希?
【发布时间】: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

所以我的问题是:

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

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

  2. 空的散列和数组字面量是否由标记指针表示?它们甚至会导致任何分配发生吗?

【问题讨论】:

  • 如果[] 总是同一个对象,你能想象所有可怕的副作用吗?除非您建议 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


【解决方案1】:

这是错失的优化机会吗?

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

class Array
  EMPTY_ARRAY = [].freeze
  
  def freeze
    empty? ? EMPTY_ARRAY : super
  end
end

空物体非常小。与您的程序使用内存的其他所有内容相比,它们使用大量内存是一种极端情况。

我了解通常使用空哈希和数组字面量来初始化将立即变异的值。

因此,将写入时复制添加到空哈希和数组可能会减慢速度。

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

冻结它们意味着您提前知道它们将保持空置状态,这种情况极为罕见。拥有这么多已知的空哈希和数组以致成为性能问题是一种极端情况。不断的变通方法似乎很好。

【讨论】:

  • 也许是empty? ? EMPTY_ARRAY : super?
猜你喜欢
  • 2013-08-06
  • 2020-10-09
  • 1970-01-01
  • 1970-01-01
  • 2014-07-02
  • 2011-08-31
  • 2021-03-20
  • 2014-09-22
  • 1970-01-01
相关资源
最近更新 更多