- 主题:请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
虽然已经过了好多天,不过我也想参与讨论这个问题。
依我浅见,虽然楼主关注的重点是Rc<RefCell<T>>,但我却想
关注所有权树这个东西。我的看法是,所有权树、&、immutability,
这些是好东西,也是螃蟹书想要表达的。它们好就好在,节省心智
开销,把复杂性收束在代码的局部。
如果使用Rc<Refcell<T>>,即便不考虑weak指针循环引用之类的问题,
那也是把一定量的数据开放给了大半个项目,乃至开放给了全局,
项目的各个部分都有可能访问到各个部分。这样表面上看起来方便,
但其实复杂性变大以后就很麻烦了。
immutability是好东西,进而&也要好于&mut。那么如果好几个主体
要访问修改同一个东西该怎么办呢?那么它往往就已经不是同一个东西了,
可以拷贝成多个“不可变的数据副本”,让多个主体去访问。
这样虽然会增大内存开销,但是只要此类数据本体尺寸不大就还是好用的。
那么如果数据尺寸很大,以及大家都一定要修改同一个核心版本该怎么办
呢?这时候实际上就应该用数据库(包括SQL的和non-SQL的)或者消息队
列去管理了。
那么如果这种数据又非常地特殊,只有这个项目的领域知识里存在,该怎
么办呢?那么我想,首先其实不能被SQL表达的领域数据是……比较少见
的,往往即便有多年经验的老码农,也不见得能真正理解到SQL表达能力
的极限。其次,这时候要做的是把项目拆分成好几个进程,其中管理一
个“大家都要访问、读写、讲究高频访问效率”的庞大复杂单一核心数
据结构,这本身就是一个项目的独立模块,是要能接受连接请求的,连接
池之类的技术都要用进去。这才对得起它的复杂程度,而不是仅仅用Rc
来“让大家都能访问它”。
总之,我的看法是,
1. 正常情况下,使用&。连&mut都要尽量节制。
2. 需要共享的数据,应该把不可变的数据作为参数来相互传递
3. 对于需要多方访问、修改的复杂数据,使用数据库
4. 连数据库都难以表达的核心复杂高性能数据,安排开发一个单独的
子项目,作为独立模块进程来管理
实际上在很多情况下,我就连写java都是这么干的。例如“不可变的
数据作为参数来传递”,背后就是data-object的设计模式罢了。
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 团队正在迁往rust,有两种关于数据结构风格的意见:
: 1. 按照螃蟹书的风格建议,既然使用了rust,就需要先费心根据需求设计好所有权树,尽量少用Rc<RefCell<T>>,多用&和&mut解决问题
: 2. 因为业务需求变化不可预测,现在设计的所有权树不一定适应将来的需求,比如一个所有权树的根,将来要变成Rc多个主体拥有,哪个主体先死都不一定,那么现在就尽量多使用Rc<RefCell<T>>,其实就是把rust当作python来用,大部分重要的数据结构变量都进堆,都用Rc保证可
: ...................
--
修改:wolfgang FROM 101.93.22.*
FROM 101.93.22.*
结构清晰心智模型简单,和压榨性能,有时候就是矛盾的。你说的这个方式,把共享的可变资源搞成独立模块甚至搞到数据库里面去,就会慢啊。在性能敏感的场景下,跑一个东西,也许就是10秒和十分钟的区别呢
【 在 wolfgang (狂云) 的大作中提到: 】
: 标 题: Re: 请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
: 发信站: 水木社区 (Tue Dec 14 10:14:06 2021), 站内
:
: 虽然已经过了好多天,不过我也想参与讨论这个问题。
:
: 依我浅见,虽然楼主关注的重点是Rc<RefCell<T>>,但我却想
: 关注所有权树这个东西。我的看法是,所有权树、&、immutability,
: 这些是好东西,也是螃蟹书想要表达的。它们好就好在,节省心智
: 开销,把复杂性收束在代码的局部。
:
: 如果使用Rc<Refcell<T>>,即便不考虑weak指针循环引用之类的问题,
: 那也是把一定量的数据开放给了大半个项目,乃至开放给了全局,
: 项目的各个部分都有可能访问到各个部分。这样表面上看起来方便,
: 但其实复杂性变大以后就很麻烦了。
:
: immutability是好东西,进而&也要好于&mut。那么如果好几个主体
: 要访问修改同一个东西该怎么办呢?那么它往往就已经不是同一个东西了,
: 可以拷贝成多个“不可变的数据副本”,让多个主体去访问。
: 这样虽然会增大内存开销,但是只要此类数据本体尺寸不大就还是好用的。
:
: 那么如果数据尺寸很大,以及大家都一定要修改同一个核心版本该怎么办
: 呢?这时候实际上就应该用数据库(包括SQL的和non-SQL的)或者消息队
: 列去管理了。
:
: 那么如果这种数据又非常地特殊,只有这个项目的领域知识里存在,该怎
: 么办呢?那么我想,首先其实不能被SQL表达的领域数据是……比较少见
: 的,往往即便有多年经验的老码农,也不见得能真正理解到SQL表达能力
: 的极限。其次,这时候要做的是把项目拆分成好几个进程,其中管理一
: 个“大家都要访问、读写、讲究高频访问效率”的庞大复杂单一核心数
: 据结构,这本身就是一个项目的独立模块,是要能接受连接请求的,连接
: 池之类的技术都要用进去。这才对得起它的复杂程度,而不是仅仅用Rc
: 来“让大家都能访问它”。
:
: 总之,我的看法是,
:
: 1. 正常情况下,使用&。连&mut都要尽量节制。
: 2. 需要共享的数据,应该把不可变的数据作为参数来相互传递
: 3. 对于需要多方访问、修改的复杂数据,使用数据库
: 4. 连数据库都难以表达的核心复杂高性能数据,安排开发一个单独的
: 子项目,作为独立模块进程来管理
:
: 实际上在很多情况下,我就连写java都是这么干的。例如“不可变的
: 数据作为参数来传递”,背后就是data-object的设计模式罢了。
:
:
: 【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: : 团队正在迁往rust,有两种关于数据结构风格的意见:
: : 1. 按照螃蟹书的风格建议,既然使用了rust,就需要先费心根据需求设计好所有权树,尽量少用Rc<RefCell<T>>,多用&和&mut解决问题
: : 2. 因为业务需求变化不可预测,现在设计的所有权树不一定适应将来的需求,比如一个所有权树的根,将来要变成Rc多个主体拥有,哪个主体先死都不一定,那么现在就尽量多使用Rc<RefCell<T>>,其实就是把rust当作python来用,大部分重要的数据结构变量都进堆,都用Rc保证可
: : ...................
:
: --
: 热二定律比动量守恒更高
: 它告诉我们世界的原初是恨
:
: 爱, 就是对恨的战斗!
:
:
: ※ 修改:·wolfgang 于 Dec 14 11:45:01 2021 修改本文·[FROM: 101.93.22.*]
: ※ 来源:·水木社区 mysmth.net·[FROM: 101.93.22.*]
--
修改:wolfgang FROM 101.93.22.*
FROM 123.120.160.*
是啥类型的项目?可以稍微透露一下吗? :)
我有这样一些不成熟的想法哈……
首先,既然为了压榨性能,不惜承受心智模型负担,那么,
&的性能比Rc高,所以应该弃绝Rc。为了性能嘛。(当然我
个人不是压榨性能的爱好者,只不过Rc确实不适合用来压
榨性能。)
其次,80%的CPU时间是用在20%的代码行数上的,这些代码是
hot-part。这些hot部分应该特殊对待,不宜用Rc来让它们和
其它代码访问同样的数据。
再次,拆模块是性能之友,因为scalability是性能之友嘛。
把模块拆好,这样才好确定哪些模块可以复制部署到多台机
器上、使用更多的CPU核心数。
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 结构清晰心智模型简单,和压榨性能,有时候就是矛盾的。你说的这个方式,把共享的可变资源搞成独立模块甚至搞到数据库里面去,就会慢啊。在性能敏感的场景下,跑一个东西,也许就是10秒和十分钟的区别呢
--
修改:wolfgang FROM 101.93.22.*
FROM 101.93.22.*
【 在 wolfgang (狂云) 的大作中提到: 】
: 标 题: Re: 请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
: 发信站: 水木社区 (Wed Dec 15 12:51:05 2021), 站内
:
: 是啥类型的项目?可以稍微透露一下吗? :)
EDA方面的项目,典型场景是要构建一个层次化的几何体的树,在里面用各种算法去布局布线,也就是调整几何体的位置、增加用于连线的新几何体、检测矩形碰撞、连通性等等。典型规模大概在几百万个矩形这个量级上,或者更大。
:
:
: 我有这样一些不成熟的想法哈……
:
: 首先,既然为了压榨性能,不惜承受心智模型负担,那么,
: &的性能比Rc高,所以应该弃绝Rc。为了性能嘛。(当然我
: 个人不是压榨性能的爱好者,只不过Rc确实不适合用来压
: 榨性能。)
是的,这个主题开头几个帖子我也表达了对Rc性能的担心。而且用Rc<RefCell<T>>写了两周,已经出现了较难控制的循环引用内存泄漏问题。
现在倾向于使用slotmap这类的arena方案,就是用array index来代替指针/引用/Rc。性能损失也是会有的,因为毕竟中间多了一次根据下标做线性寻址的操作,slotmap这类方案为了避免ABA问题,还要额外储存version信息并且比对。直觉上这个和Rc+RefCell相比,性能损失应该差不多,谁多谁少说不准。但是避免了内存泄漏,心智模型也更简单。
slotmap arena方案有点像游戏开发里面的ECS,外面传来传去的只是一个entity id(下标),数据都在components里,行为都在system里。
:
: 其次,80%的CPU时间是用在20%的代码行数上的,这些代码是
: hot-part。这些hot部分应该特殊对待,不宜用Rc来让它们和
: 其它代码访问同样的数据。
:
: 再次,拆模块是性能之友,因为scalability是性能之友嘛。
: 把模块拆好,这样才好确定哪些模块可以复制部署到多台机
: 器上、使用更多的CPU核心数。
如果你之前说的拆模块只是指同一机器上、甚至同一进程里的功能对象封装的话,那当然是肯定要做的啦。
:
:
: 【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: : 结构清晰心智模型简单,和压榨性能,有时候就是矛盾的。你说的这个方式,把共享的可变资源搞成独立模块甚至搞到数据库里面去,就会慢啊。在性能敏感的场景下,跑一个东西,也许就是10秒和十分钟的区别呢
:
:
: --
: 热二定律比动量守恒更高
: 它告诉我们世界的原初是恨
:
: 爱, 就是对恨的战斗!
:
:
: ※ 修改:·wolfgang 于 Dec 15 12:54:06 2021 修改本文·[FROM: 101.93.22.*]
: ※ 来源:·水木社区 mysmth.net·[FROM: 101.93.22.*]
--
修改:wolfgang FROM 101.93.22.*
FROM 123.120.160.*
啊这……
请容我多嘴一句:slotmap与关系数据库其实已经只有一步之遥了
啊。
关系数据库里的“关系”,基本形式就是:我的字段里存着你的id
(像极了array index),我就记住了你与我的关系。
而一旦用上关系数据库,至少就可以立刻用上数据库提供的id索引。
从线性寻址变成B树寻址,这对性能是有很大益处的。
而对于数据一致性问题,transaction可以帮上一些忙,虽然可能
不能全都轻易解决。
读写硬盘确实比较慢,但可以使用内存数据库嘛。
总之,当我们的业务需要对几百万条关系复杂的数据进行管理的话,
那么我们干的其实就是数据库的活。即便硬挺着不用数据库,迟早
也要把数据库面对的种种挑战都自己解决一遍。
当然,这只是我的一家之言……
另外,关于“所有权树”,我其实有一点不同的想法。我觉得,
rust和modern c++所讨论的所有权,它不是形成一个所有权的树,
而是所有权的流,是个flow。
其中一个很典型的现象就是,一个函数,接受输入参数,返回结果
值,这个结果值的所有权,也一起返回了,交给了调用者。
这些数值、数据结构,进而投入新的运算,把所有权交出去,然后
得到返回值,也拿到了新值的所有权。所有权就这样流动,用完了
以后就被RAII回收。这样的做法,全程都是const的,旧值不变化,
新值被产出,这是符合很多数学计算思想的思维方式。
我猜想,当我们在设计布线的时候,或许:旧的布线是一种输入,
新的元件需求是另一种输入,进而,新的布线是计算得到的输出。
所有权是在旧的值到新的值之间流动,每一个值(布线方案)都是
一个对象,旧的对象被RAII回收,新的对象投入更多的计算与优化。
但是每一个此类对象,一旦生成,就不会变化,不必再有&mut发生,
也不会有用到Rc的需求。
这样或许可以节省许多心智开销,而且性能方面吃亏很少。
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: EDA方面的项目,典型场景是要构建一个层次化的几何体的树,在里面用各种算法去布局布线,也就是调整几何体的位置、增加用于连线的新几何体、检测矩形碰撞、连通性等等。典型规模大概在几百万个矩形这个量级上,或者更大。
: 是的,这个主题开头几个帖子我也表达了对Rc性能的担心。而且用Rc<RefCell<T>>写了两周,已经出现了较难控制的循环引用内存泄漏问题。
: 现在倾向于使用slotmap这类的arena方案,就是用array index来代替指针/引用/Rc。性能损失也是会有的,因为毕竟中间多了一次根据下标做线性寻址的操作,slotmap这类方案为了避免ABA问题,还要额外储存version信息并且比对。直觉上这个和Rc+RefCell相比,性能损失应该差不
: ...................
--
修改:wolfgang FROM 101.88.39.*
FROM 101.88.39.*
【 在 wolfgang (狂云) 的大作中提到: 】
: 标 题: Re: 请教个rust基本问题,Rc<RefCell<T>>有啥坏处?
: 发信站: 水木社区 (Sun Dec 19 21:41:59 2021), 站内
:
: 啊这……
:
: 请容我多嘴一句:slotmap与关系数据库其实已经只有一步之遥了
: 啊。
:
: 关系数据库里的“关系”,基本形式就是:我的字段里存着你的id
: (像极了array index),我就记住了你与我的关系。
是的,arena key/index 就是数据库里面的row id。但是还是不一样的,数据库是为硬盘慢速读写而设计的,所以b树啊、索引啊,核心都是为了减少硬盘读写次数。而rust开发里更一般的场景的数据量没到内存放不下的程度,所以都是内存存取,所以还是要比数据库简单很多的。
:
: 而一旦用上关系数据库,至少就可以立刻用上数据库提供的id索引。
: 从线性寻址变成B树寻址,这对性能是有很大益处的。
这个不对吧?所谓线性寻址就是连续空间用乘法计算指针偏移量,其实就是数组按下标寻址,还是要比b树啊哈希啊快多了
:
: 而对于数据一致性问题,transaction可以帮上一些忙,虽然可能
: 不能全都轻易解决。
:
: 读写硬盘确实比较慢,但可以使用内存数据库嘛。
用redis的背后也是从key 哈希到地址啊,可以简化理解为一个大大的hashmap,还是比不上同一进程内的数组下标寻址的。
:
: 总之,当我们的业务需要对几百万条关系复杂的数据进行管理的话,
: 那么我们干的其实就是数据库的活。即便硬挺着不用数据库,迟早
: 也要把数据库面对的种种挑战都自己解决一遍。
:
slotmap, generational-arena, ECS,数据库,共性就是“按id查表”,这的确是很相似的。如果数据量增大到机器内存装不下的程度,那的确会往数据库方向演变;如果构建的最终结果、中间结果有持久化的需求,的确也用得上数据库。不过如果需求是对数据进行极为频繁的读写的话,现成的数据库产品基本上就是性能杀手,所以大量工作尽量还是在内存里完成为好,实在不行就把硬盘上的数据库当作虚拟内存用来整块交换,也许还不错。。。
: 当然,这只是我的一家之言……
:
:
:
:
: 另外,关于“所有权树”,我其实有一点不同的想法。我觉得,
: rust和modern c++所讨论的所有权,它不是形成一个所有权的树,
: 而是所有权的流,是个flow。
:
: 其中一个很典型的现象就是,一个函数,接受输入参数,返回结果
: 值,这个结果值的所有权,也一起返回了,交给了调用者。
:
: 这些数值、数据结构,进而投入新的运算,把所有权交出去,然后
: 得到返回值,也拿到了新值的所有权。所有权就这样流动,用完了
: 以后就被RAII回收。这样的做法,全程都是const的,旧值不变化,
: 新值被产出,这是符合很多数学计算思想的思维方式。
:
: 我猜想,当我们在设计布线的时候,或许:旧的布线是一种输入,
: 新的元件需求是另一种输入,进而,新的布线是计算得到的输出。
:
: 所有权是在旧的值到新的值之间流动,每一个值(布线方案)都是
: 一个对象,旧的对象被RAII回收,新的对象投入更多的计算与优化。
:
: 但是每一个此类对象,一旦生成,就不会变化,不必再有&mut发生,
: 也不会有用到Rc的需求。
:
: 这样或许可以节省许多心智开销,而且性能方面吃亏很少。
:
你说的这个流,和所有权树不是一个层次的事情。所有权树是从内存空间角度看的,所有权其实就是“释放资源的责任归属”,堆内存里每一项资源都有一个owner来负责销毁回收它,这个owner如果也在堆上,那就存在owner的owner....最终责任会归结到栈上或者static区上的某个变量。而如果一个owner是一个结构体或者向量的话,它会同时负责多个成员的销毁。这就形成了一颗责任树。这就是所有权树的概念。
你说的流,是一个时间上的概念,是说上述的某一个owner在时间上会把所有权转交给另一个变量,时间上的传递。也就是时间上看,所有权树中间的每个节点都有可能会变换主体,但是这个树的形状会一直保持的。
全程const,这是函数式的概念,和所有权无关。一大把函数式语言,没有所有权的概念,没有move语义,也不妨碍它们都搞无副作用、无变量的纯函数。
全程const,无mut,这不是rust的理念。固然EDA开发圈子里面很多项目偏爱函数式,比如
chisel是scala写的,bluespec是haskell写的,但我觉得rust的平衡找得更好一些。95%的逻辑写成不变量的flow的确更简单,但是总有那5%的逻辑,用函数式写很蛋疼,编译器也没有神到可以把所有的函数式写法优化到极致的程度。所以原则应该是,尽量不要用mut,但是也不要教条。
:
:
: 【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: : EDA方面的项目,典型场景是要构建一个层次化的几何体的树,在里面用各种算法去布局布线,也就是调整几何体的位置、增加用于连线的新几何体、检测矩形碰撞、连通性等等。典型规模大概在几百万个矩形这个量级上,或者更大。
: : 是的,这个主题开头几个帖子我也表达了对Rc性能的担心。而且用Rc<RefCell<T>>写了两周,已经出现了较难控制的循环引用内存泄漏问题。
: : 现在倾向于使用slotmap这类的arena方案,就是用array index来代替指针/引用/Rc。性能损失也是会有的,因为毕竟中间多了一次根据下标做线性寻址的操作,slotmap这类方案为了避免ABA问题,还要额外储存version信息并且比对。直觉上这个和Rc+RefCell相比,性能损失应该差不
: : ...................
:
: --
: 热二定律比动量守恒更高
: 它告诉我们世界的原初是恨
:
: 爱, 就是对恨的战斗!
:
:
: ※ 修改:·wolfgang 于 Dec 19 21:53:03 2021 修改本文·[FROM: 101.88.39.*]
: ※ 来源:·水木社区 mysmth.net·[FROM: 101.88.39.*]
--
修改:wolfgang FROM 101.88.39.*
FROM 123.120.160.*
赞,你说的很有道理~
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 是的,arena key/index 就是数据库里面的row id。但是还是不一样的,数据库是为硬盘慢速读写而设计的,所以b树啊、索引啊,核心都是为了减少硬盘读写次数。而rust开发里更一般的场景的数据量没到内存放不下的程度,所以都是内存存取,所以还是要比数据库简单很多的。
: 这个不对吧?所谓线性寻址就是连续空间用乘法计算指针偏移量,其实就是数组按下标寻址,还是要比b树啊哈希啊快多了
: 用redis的背后也是从key 哈希到地址啊,可以简化理解为一个大大的hashmap,还是比不上同一进程内的数组下标寻址的。
: ...................
--
FROM 101.88.39.*