新手问题 感觉 Ruby 缺少显式创建引用的功能,有没有大佬可以给核心开发者建议添加一下

lilijreey · 2020年08月15日 · 最后由 a3030546 回复于 2020年08月18日 · 1868 次阅读

ruby 中有值和引用语义. 比如 数字都是值语义也就是说

a=1
b=a # 值拷贝
b=4
#a 还是1

很多时候这种设定是希望的行为. 但是如果我们希望 b 是 a 的引用这种语义时,ruby 好像没法做到. 是否可以在 ObjectBase 对象上添加 .ref 方法。这个方法返回该对象的引用 这样我们就可以

a=1
b=a.ref # 创建引用
b=4
#a 也是4

如果有了主动创建 ref 语义的功能,可以优化很多算法的性能 比如。

a = [1,3,4,5,6]
#现在想从 a[2] 开始找是否有元素1出现
a[2..].index(2) 这样可以但是会创建一个临时对象,很不划算
如果用ref
a.ref[2..].index(2) 这样就是在a自己身上做搜索,之后生成一个代价很小的引用对象, 没有大量元素复制. 性能会提高几十倍不止.

大家怎么看

非常在意性能不要用 ruby,用 C

你举的例子可以用 each_with_index 或者 enumerator 实现。

spike76 回复

上面只是说不要做不必要的事情,和非常在意性能有个毛关系。 再说用 ruby 和在意性能不矛盾,看看 ruby3x3, JIT 这些优化,不懂就不要装懂少丢点人。

我是看你打比赛可能会以性能做为结果评比,觉得不如用 C 为好。顺便提了你下面举的代码例子问题。没想到这还能被喷丢人。前几天还回答了你的问题,当时还有感谢,没想到变脸这么快。

@Rei @huacnli 希望管理员来评个理

https://bugs.ruby-lang.org/ 是开放的,不需要大佬,先搜索有没有同类 issus,再阅读提交指引,然后提交特性请求,core team 会回复。

就你的例子而言,我们做一个测试

require 'benchmark'

n = 100_000
arr = 1.upto(n).to_a
index = n / 2

puts index

Benchmark.bm do |x|
  x.report { 100.times { arr.index(index) } }
  x.report { 100.times { arr[2..].index(index) } }
end

ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19] 上的结果

    user     system      total        real
0.036552   0.000022   0.036574 (  0.036579)
0.035222   0.000063   0.035285 (  0.035303)
    user     system      total        real
0.034097   0.000060   0.034157 (  0.034210)
0.030318   0.000049   0.030367 (  0.030414)
    user     system      total        real
0.039931   0.000083   0.040014 (  0.040158)
0.035147   0.000157   0.035304 (  0.035333)

后者比前者性能略高(考虑误差性能大致一致)

换一种 benchmark 做法

require 'benchmark/ips'

n = 100_000
arr = 1.upto(n).to_a
index = n / 2

puts index

Benchmark.ips do |x|
  x.config(time: 10, warmup: 2)

  x.report("arr.index(index)") { arr.index(index) }
  x.report("arr[2..].index(index)") { arr[2..].index(index) }

  x.compare!
end

结果

Warming up --------------------------------------
    arr.index(index)   329.000  i/100ms
arr[2..].index(index)
                       317.000  i/100ms
Calculating -------------------------------------
    arr.index(index)      3.486k (± 3.5%) i/s -     34.874k in  10.018586s
arr[2..].index(index)
                          3.455k (± 1.6%) i/s -     34.553k in  10.002732s

Comparison:
    arr.index(index):     3485.6 i/s
arr[2..].index(index):     3455.3 i/s - same-ish: difference falls within error

两种写法的性能表现几乎相同(考虑误差)

结果来看两种写法的性能并无差别,你的假设证伪。

更新:

换了 Float 一样成立

require 'benchmark'

n = 100_000.0
arr = 1.upto(n).to_a
index = n / 2

puts index

Benchmark.bm do |x|
  x.report { 100.times { arr.index(index) } }
  x.report { 100.times { arr[2..].index(index) } }
end
    user     system      total        real
0.196415   0.000087   0.196502 (  0.196542)
0.188249   0.000104   0.188353 (  0.188391)

此外

ruby 中有值和引用语义

这句话明显也是错的,Ruby 语言已经明确了只存在引用,就你以 Integer 的例子,证伪很容易

2.7.1 :021 > a = 1
2.7.1 :022 > a.object_id
 => 3
2.7.1 :023 > b = a
2.7.1 :024 > b.object_id
 => 3

a 和 b 都指向同一个 Integer 对象,并没有发生值拷贝

你对 b = a 的理解也有误区,他的意思是将 a 指向的对象 binding(绑定)到 b 上。

jasl 回复

integer 是特例,object id 是定值

hooopo 回复

反驳的点是 他认为 b = a 是值拷贝,事实上 Ruby 不存在值语义, 换 Float 结果依旧成立

2.7.1 :027 > a = 1.1
2.7.1 :028 > a.object_id
 => -32425917317067566
2.7.1 :029 > b = a
2.7.1 :030 > b.object_id
 => -32425917317067566

我们再来看看是否如你所说产生了大量的小对象

n = 100_000
index = n / 2

GC.disable

puts "Checkpoint 1"
pp ObjectSpace.count_objects

arr = 1.upto(n).to_a
arr.index(index)

puts "Checkpoint 2"
pp ObjectSpace.count_objects

arr1 = arr[2..]
arr1.index(index)

puts "Checkpoint 3"
pp ObjectSpace.count_objects

注意为了避免 irb 的干扰(语句打印到控制台会产生大量对象)语句这段代码要么保存成文件执行,要么用 eval 来执行

另外注意,我们在代码里关闭了 GC,也就是说产生的任何对象,都不会被回收

Checkpoint 1
{:TOTAL=>48914,
 :FREE=>3078,
 :T_OBJECT=>2032,
 :T_CLASS=>998,
 :T_MODULE=>76,
 :T_FLOAT=>4,
 :T_STRING=>23003,
 :T_REGEXP=>323,
 :T_ARRAY=>4881,
 :T_HASH=>274,
 :T_STRUCT=>1000,
 :T_BIGNUM=>2,
 :T_FILE=>5,
 :T_DATA=>567,
 :T_MATCH=>248,
 :T_COMPLEX=>1,
 :T_SYMBOL=>58,
 :T_IMEMO=>12265,
 :T_ICLASS=>99}
Checkpoint 2
{:TOTAL=>49321,
 :FREE=>303,
 :T_OBJECT=>2161,
 :T_CLASS=>1016,
 :T_MODULE=>78,
 :T_FLOAT=>4,
 :T_STRING=>24893,
 :T_REGEXP=>323,
 :T_ARRAY=>5357,
 :T_HASH=>284,
 :T_STRUCT=>1002,
 :T_BIGNUM=>2,
 :T_FILE=>7,
 :T_DATA=>615,
 :T_MATCH=>248,
 :T_COMPLEX=>1,
 :T_SYMBOL=>58,
 :T_IMEMO=>12867,
 :T_ICLASS=>102}
Checkpoint 3
{:TOTAL=>49729,
 :FREE=>167,
 :T_OBJECT=>2290,
 :T_CLASS=>1016,
 :T_MODULE=>78,
 :T_FLOAT=>4,
 :T_STRING=>25066,
 :T_REGEXP=>323,
 :T_ARRAY=>5512,
 :T_HASH=>286,
 :T_STRUCT=>1003,
 :T_BIGNUM=>2,
 :T_FILE=>7,
 :T_DATA=>656,
 :T_MATCH=>248,
 :T_COMPLEX=>1,
 :T_SYMBOL=>58,
 :T_IMEMO=>12910,
 :T_ICLASS=>102}

观察结果,并没有产生大量的元素复制,假设证伪。

Ruby 默认都是引用操作,你自定义一个类,测试一下就知道 array index 也是引用操作,不存在你说的性能消耗问题:

class Foo
    attr_accessor :bar
end

a = [Foo.new, Foo.new, Foo.new]
b = a[1..-1]

b[0].bar = 42
a[1].bar == b[0].bar
=> true
jasl 回复

我说不对我知道,但是实际上的效果可以值拷贝一致

jasl 回复

牛逼,事实说明并不是我想的那样消耗性能。学习了。我看底层好像是有个 Object 内存池的实现。具体的实现可能已经做了优化

lilijreey 回复

Ruby 不存在 值 这个概念,我不知道你说的值拷贝在 Ruby 上会是什么样子

jasl 回复

就是 对 Int 类型 a=3; b=a, 可以认为是值拷贝。虽然本质上只是 bind 改变了。

lilijreey 回复

a 和 b 指向同一个对象,请问“拷贝”发生在哪里?

lilijreey 回复

那么优化性能难道不是以 事实 存在性能瓶颈为前提进行的吗?

题外话,Ruby 有 WeakRef

lilijreey 回复

我很好奇你是怎么理解的我在一楼的回复,能让你这么怒火中烧,指责我“不懂就不要装懂少丢点人”。

我之前回答过你不少问题,看出你好像是用 ruby 做编程比赛,并且挺在意数据结构和性能,而且这个主题描述来看你想要的就是个指针概念,所以我觉得还不如用 C。

再说了,说 ruby 性能不如 C,怎么就成了“不懂装懂”了,顶多算个偏题。

题主咱俩得把这个问题沟通清楚,被人莫名其妙在公共场合指责一顿,是相当郁闷不爽的一件事,况且这个人几天前还对我其它的回答相当友好

我认为 ruby 这语言最核心的一点是,同其他语言相比,非常在意的表达的高层性,或者说越接近人类语言越好。

加引用本身就不是人类语言语法的一部分,所以官方肯定是不可能加的,从 Ruby 3 加的类型检查,都是分文件,而不是用惯常的a: integer这样的其他语言语法就可以看出官方的选择。

Ruby 这边的确在不断优化性能,但是不可能为了优化性能牺牲表达的高层性。

ericguo 回复

引用不妨碍表达性,就和你用 ruby 要知道 ruby 是怎么实现的一样。ruby 不是表示语言,你说的更不不成立

spike76 回复

所答非所问,不过不是你理解有问题,或者我表达有问题,那我就会认为你是不懂装懂。至少在这个问题上。你没有给出我认为满意的答案。可以对比@jasl ,他给的答案就正面回答了我的问题,至少是部分解答了。

jasl 回复

从实时看你说的无疑都正确,我的表述有一些错误,但是我的表述只是在描述一种情况,类比这种概念。不能完扣子去理解,要理解我说话的语境。就这么简单。我说的是 ruby 引用或者(C++ 这种引用)概念。

lilijreey 回复

我不觉得这是扣帽子,你没有拿出证据证明这里有瓶颈,谈何优化?况且我不觉得你的态度很友善

为什么要我去理解你的情况和语境?表述不清楚是你的错,不是我的错

Ruby 的引用和 Java 的引用并无二致,编程语言上的“引用”的理解都是一样的,如果你认为概念有别,那么是你对引用的理解有误区。

Ruby 只有引用,这个是语言定义上就明确提到的

jasl 回复

吸取教训,下回注意😓

lilijreey 回复

ruby 不是表示语言

对我来说,Ruby 基本就可以用作表示语言。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号