起因是最近有人在 skynet 邮件列表里贴了段错误 log ,从 log 显示,他在 table.sort 的比较函数里调用了 skynet 的 snax rpc 去获取远程数据。然后被 lua 无情的报了 attempt to yield across a C-call boundary 。
就 table.sort 不能 yieldable 的问题,其实在 lua 邮件列表里讨论过 。老大的说法是,这个 C 实现是递归的,想要在 C 层面保留上下文非常困难,如果勉强实现,也会大大降低正常不需要 yield 的 case 的性能,非常不划算。
通过这件事,我反而觉得 none-yieldable 的限制反而提前阻止了一个错误的实现,其实是应该庆幸的。
最容易发现的是性能问题。
在《Unix 编程艺术》一书 7.3.2 节讨论 RPC 时,这样批评道:
“ a. 无法准确预估出一个指定调用会涉及多少数据的列集和散集,b. RPC 模型往往鼓励程序员把网络交易视为无成本行为。即使在某个事务处理接口上只额外增加一个来回,往往也增加足够多的网络延迟,完全抵消了解析或列集的开销。”
这段话的语境是把 RPC 和传统的消息通讯方式相比较,针对那些认为 RPC 的效率更高的观点说的。
即使不看效率,单看 RPC 给编程实现带来的方便性,其实最终是增加了,而不是减少了系统的复杂度。原文引用:
“支持 RPC 的主要理由同时恰恰证明了 RPC 增加了,而不是降低了程序的全局复杂度。”……(那些基于 RPC 的框架) “随着人们使用经验的增长,都已经从 Unix 世界的视野中消失了。这完全可能是因为这些方法能够解决的问题还不如它们引发的问题多。”
关于 RPC 的批评,还可以看这篇 paper A Critique of the Remote Procedure Call Paradigm
snax 是对 skynet api 做的一个 rpc 封装,愿意是让使用的人门槛更低。但做完我就后悔了,rpc 只是表面上简单而已,但我自己却直觉的排斥这种做法,所以也没有把它作为 skynet 其它基础组件的基础。当用户开始以 RPC 方式调用时,他真的有一种不就是调了个函数的错觉。
针对 table.sort 这个比较函数里做 RPC 调用这件事说,对 n 个数据排序,table.sort 使用的是传统的快速排序,一种内部排序算法。它注重的是减少比较次数,而视获得数据的成本为 0 。在排序过程中,每个元素都不只一次的取出来比较。而 RPC 获取元素的成本显然是很高的,所以初看来,这里造成了严重的性能问题。
但如果仅仅是性能问题倒还好,可这样做其实正确性也无法保证。
如果要排序的元素在远程,也就是在另一个地方变化(如果是不变量也不必储远程获取了)。那么显然排序过程中,这些量是易变的,也就是你多次取同一个元素是可能不一样的。这样,sort 算法无法避免出现 a > b , b > c , 而 c > a 的情况出现。排序算法是无法正常工作的。
用一个通俗点的例子:如果你想按全国所有中学的学生人数对学校做一个排序。你是先把所有学校的人数都做一个调研,记在一起再排序;还是对这些信息一无所知的情况下,先按排序算法流程走,算法要求比较某两个学校人数的时候,再去调查这两个学校的数据?