Monix Coeval.memoize递归调用栈溢出原因咨询
为什么带
.memoize的Coeval递归会栈溢出,去掉就正常? 不带.memoize的栈安全原理
你的代码去掉.memoize后能处理n=10000,核心是Coeval.defer和map的组合利用了Coeval的栈安全执行模型(通常基于蹦床/trampoline机制)。defer会把递归调用包装成惰性计算单元,不会立即执行。当调用.value时,Coeval不会像普通递归那样一层层压栈,而是把每一步计算拆成可迭代的小任务——执行完一步就释放对应的栈帧,再处理下一步。哪怕递归深度到10000,也不会撑爆栈。
带.memoize时栈溢出的根源
当你在每一层递归都加上.memoize后,逻辑就完全变了。memoize的作用是缓存Coeval的计算结果,但它的实现逻辑是:第一次调用.value时,必须同步完整计算出原始Coeval的结果,再把结果存起来。
具体到你的代码,memoizeCoeval(n)返回的是Coeval.defer(memoizeCoeval(n-1)).map(_+1).memoize。当计算n=10000的.value时:
- 这个memoized Coeval发现没有缓存,于是开始计算原始Coeval(也就是
defer(...)部分)的结果; - 原始Coeval的
.value会触发memoizeCoeval(9999)的调用,而它又是一个memoized Coeval,于是重复步骤1——它也需要先计算自己的原始Coeval,也就是memoizeCoeval(9998)的结果; - 这样一层层嵌套下去,每一层都需要等待下一层的计算完成才能继续,就像普通的递归函数调用一样,每一层都会在JVM的调用栈上留下一个帧。当n=10000时,栈帧数量远远超过了JVM默认的栈大小(通常几百到几千层),自然就出现栈溢出了。
简单说:memoize打破了Coeval原本的栈安全执行模式,把“迭代式的惰性计算”变回了“传统的递归调用栈”,深度太大就撑爆了栈。
内容的提问来源于stack exchange,提问作者Kamil Kloch
相关产品推荐
相关产品推荐

