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 和在意性能不矛盾,看看 ruby3x3, JIT 这些优化,不懂就不要装懂少丢点人。
我是看你打比赛可能会以性能做为结果评比,觉得不如用 C 为好。顺便提了你下面举的代码例子问题。没想到这还能被喷丢人。前几天还回答了你的问题,当时还有感谢,没想到变脸这么快。
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
上。
反驳的点是 他认为 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
我很好奇你是怎么理解的我在一楼的回复,能让你这么怒火中烧,指责我“不懂就不要装懂少丢点人”。
我之前回答过你不少问题,看出你好像是用 ruby 做编程比赛,并且挺在意数据结构和性能,而且这个主题描述来看你想要的就是个指针概念,所以我觉得还不如用 C。
再说了,说 ruby 性能不如 C,怎么就成了“不懂装懂”了,顶多算个偏题。
题主咱俩得把这个问题沟通清楚,被人莫名其妙在公共场合指责一顿,是相当郁闷不爽的一件事,况且这个人几天前还对我其它的回答相当友好
我认为 ruby 这语言最核心的一点是,同其他语言相比,非常在意的表达的高层性,或者说越接近人类语言越好。
加引用本身就不是人类语言语法的一部分,所以官方肯定是不可能加的,从 Ruby 3 加的类型检查,都是分文件,而不是用惯常的a: integer
这样的其他语言语法就可以看出官方的选择。
Ruby 这边的确在不断优化性能,但是不可能为了优化性能牺牲表达的高层性。
所答非所问,不过不是你理解有问题,或者我表达有问题,那我就会认为你是不懂装懂。至少在这个问题上。你没有给出我认为满意的答案。可以对比@jasl ,他给的答案就正面回答了我的问题,至少是部分解答了。
从实时看你说的无疑都正确,我的表述有一些错误,但是我的表述只是在描述一种情况,类比这种概念。不能完扣子去理解,要理解我说话的语境。就这么简单。我说的是 ruby 引用或者(C++ 这种引用)概念。
我不觉得这是扣帽子,你没有拿出证据证明这里有瓶颈,谈何优化?况且我不觉得你的态度很友善
为什么要我去理解你的情况和语境?表述不清楚是你的错,不是我的错
Ruby 的引用和 Java 的引用并无二致,编程语言上的“引用”的理解都是一样的,如果你认为概念有别,那么是你对引用的理解有误区。
Ruby 只有引用,这个是语言定义上就明确提到的