方法定义中的Ruby Splat运算符占用更多内存

the_spectator

在我们的代码库上进行优化时,我们尝试使用bang方法减少有意义的对象分配,但是我们在基准测试中观察到分配的对象数量减少了,但总体内存大小却增加了。

复制脚本:

# frozen_string_literal: true

require 'bundler/inline'

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem 'benchmark-memory', '0.1.2'
end

require 'benchmark/memory'

def with_bang(*methods)
  methods.tap(&:flatten!)
end

def without_bang(*methods)
  methods.flatten
end


Benchmark.memory do |x|
  x.report("with_bang") { with_bang(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o) }
  x.report("without_bang") { without_bang(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o) }
  x.compare!
end


# Output
# Ruby version: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]

# INPUT: (:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o)
# Calculating -------------------------------------
#            with_bang   160.000  memsize (     0.000  retained)
#                          1.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)
#         without_bang    80.000  memsize (     0.000  retained)
#                          2.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)

# Comparison:
#         without_bang:         80 allocated
#            with_bang:        160 allocated - 2.00x more


# INPUT: (:a, :b, :c, :d, :e, [:f, :g], :h, :i, :j, :k, :l, :m, :n, :o)
# Calculating -------------------------------------
#            with_bang   240.000  memsize (     0.000  retained)
#                          3.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)
#         without_bang   480.000  memsize (     0.000  retained)
#                          3.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)

# Comparison:
#            with_bang:        240 allocated
#         without_bang:        480 allocated - 2.00x more

在我的实验中,我相信这是由于splat运算符转换为数组所致。以下是提示我该结论的脚本。

# frozen_string_literal: true

require 'bundler/inline'

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem 'benchmark-memory', '0.1.2'
end

require 'benchmark/memory'

def with_splat(*methods)
  methods.flatten!
end

def without_splat
  methods = [:a, :b, :c, :d, :e, [:f, :g], :h, :i, :j, :k, :l, :m, :n, :o]
  methods.flatten!
end


Benchmark.memory do |x|
  x.report("with_splat") { with_splat(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o) }
  x.report("without_splat") { without_splat }
  x.compare!
end

# Output
# Ruby version: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]

# INPUT: (:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o)
# Calculating -------------------------------------
#           with_splat   160.000  memsize (     0.000  retained)
#                          1.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)
#        without_splat    40.000  memsize (     0.000  retained)
#                          1.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)

# Comparison:
#        without_splat:         40 allocated
#           with_splat:        160 allocated - 4.00x more


# INPUT: (:a, :b, :c, :d, :e, [:f, :g], :h, :i, :j, :k, :l, :m, :n, :o)
# Calculating -------------------------------------
#           with_splat   240.000  memsize (     0.000  retained)
#                          3.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)
#        without_splat   240.000  memsize (     0.000  retained)
#                          3.000  objects (     0.000  retained)
#                          0.000  strings (     0.000  retained)

# Comparison:
#           with_splat:        240 allocated
#        without_splat:        240 allocated - same

我缺少了解这种行为的什么?为何它会以这种方式运行?

谢谢!

编辑:我向包含嵌套数组的基准比较添加了新的输入。有了新的输入,我们看到的结果与以前的基准测试有所不同,我感到更加困惑!

斯特凡

让我们更仔细地检查两个数组:

require 'objspace'

def with_splat(*methods)
  ObjectSpace.dump(methods, output: open('with_splat.json', 'w'))
end

def without_splat(methods)
  ObjectSpace.dump(methods, output: open('without_splat.json', 'w'))
end

with_splat(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o)
without_splat([:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o])

ObjectSpace.dump_all(output: open('all_objects.json', 'w'))

该脚本生成3个文件:

  • with_splat.json 包含有关阵列数组的数据
  • without_splat.json 包含有关非散列数组的数据
  • all_objects.json 包含有关所有对象的数据(很多!)

with_splat.json:(格式化)

{
  "address": "0x7feb941289a0",
  "type": "ARRAY",
  "class": "0x7feb940972c0",
  "length": 15,
  "memsize": 160,
  "flags": {
    "wb_protected": true
  }
}

without_splat.json:(格式化)

{
  "address": "0x7feb941287e8",
  "type": "ARRAY",
  "class": "0x7feb940972c0",
  "length": 15,
  "shared": true,
  "references": [
    "0x7feb941328d8"
  ],
  "memsize": 40,
  "flags": {
    "wb_protected": true
  }
}

如您所见,后一个数组确实消耗较少的内存(40 vs 160),但是它也已"shared": true设置并且在内存address引用了另一个对象0x7feb941328d8

让我们all_objects.json通过jq找到该对象

$ jq 'select(.address == "0x7feb941328d8")' all_objects.json
{
  "address": "0x7feb941328d8",
  "type": "ARRAY",
  "frozen": true,
  "length": 15,
  "memsize": 160,
  "flags": {
    "wb_protected": true
  }
}

这就是实际的数组,其内存大小与上面的第一个数组相同。

请注意,此数组已"frozen": true设置。我假设Ruby在遇到数组文字时会创建这些冻结的数组。然后,它可以根据评估结果创建便宜的(更)共享阵列。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

静态方法中splat运算符的默认参数值

Ruby中使用的独立splat运算符(*)是什么?

使Ruby对象响应double splat运算符**

JavaScript与Ruby的splat运算符等效吗?

为什么splat运算符(*)仅用于方法参数?

Elixir中是否有splat运算符?

将常量传递给方法,导致语法错误。使用中的 Splat 运算符

在Python中,赋值运算符在类方法定义中作为默认值传递时是否访问类或实例变量?

JavaScript中的Splat运算符,等同于Python中的* args和** kwargs吗?

使用 splat 运算符将 array_diff_uassoc 转换为静态方法的问题

Python:Python中的Splat / unpack运算符*不能在表达式中使用吗?

在Crystal中是否可以通过splat运算符使用非Tuple对象?

==运算符在Java检查内存地址中的作用

赋值运算符会消耗Java中的内存吗?

在C ++中释放内存的问题(删除运算符)

=> Ruby中的运算符

Ruby%运算符

Ruby的&&运算符

模板值比类中定义的更多的运算符没有匹配功能

三元运算符?:定义为Ruby中的方法吗?

方法定义中的错误“传递 const List<int> 作为此参数丢弃限定符”

Double-splat运算符破坏性地修改了哈希-这是一个Ruby错误吗?

如何减少C ++中自定义类的+运算符内存消耗?

带符号除数的%运算符需要C中更多的程序存储器

TypeScript 中的 null 合并运算符 (??) 是否比 JavaScript 的浏览器支持更多?

“ ===”运算符在Ruby中做什么?

在Ruby中实现运算符重载(+ =)

+= ruby 中的运算符重载

Ruby =〜vs ===运算符