【问题标题】:Where exactly does the performance advantage of LazyEvaluation emerge from?LazyEvaluation 的性能优势究竟从何而来?
【发布时间】:2013-07-18 05:30:52
【问题描述】:

这几天研究了LazyEvaluation,主要是在性能方面,想LazyEvalutaion的性能优势体现在哪里。

阅读各种文章对我来说很不清楚,但很少有,包括

What are the advantages of Lazy Evaluation?

这是指语法树的评估。如果你评估一个 懒惰的语法树(即当需要它表示的值时),你 必须在其计算的前面步骤中执行它 整体。这是惰性求值的开销。然而,有 两个优点。 1) 如果 从未使用结果,

例如,JavaScript 实现了 if 语法。

if(true)
{
    //to evaluate
}
else
{
    //not to evaluate
}

在这种普通场景下,我们没有任何性能问题。

待评估已完成,未评估在语法树中被忽略。

但是,在某些递归循环中,例如,Tak function AKA Tarai function

函数 tak(x,y,z){ 返回 (x

由于 JS 的 Eager Evaluation 策略评估函数(参数)的必然性, if - else - not to beevaluate 控件不再起作用,tak function 的求值步数激增。

针对 Eager Evaluation(JS 或其他语言)的这一缺点,Haskell 可以毫无问题地评估 tak,并且某些 JS 库(例如 lazy.js)在特定领域表现出色比如需要递归列表管理的函数式编程。

除了无限列表,我知道这是 LazyEvaluation 性能优势的确切原因。我说的对吗?

【问题讨论】:

  • 对于 haskell 程序员来说,性能实际上是惰性求值的棘手部分之一。真正的好处在于表现力,允许程序员以多种不同的方式设计解决方案(例如,我想将这段代码表示为无限列表)并且能够将解决方案拆分为可以轻松组合的部分,灵活;在严格的语言评估问题将决定您如何构建代码。此外,您需要懒惰才能真正拥有“声明性”的语言,并且(理想情况下)一旦拥有,您就不会考虑“评估”。
  • @jberryman,感谢您的意见。我同意。最后一部分,“声明式”,除了懒惰之外,我认为我们确实需要通过事件流以 FRP 的方式将语言与 IO 结合起来。顺便说一句,我喜欢你网站上的照片。

标签: javascript haskell functional-programming lazy-evaluation


【解决方案1】:

我认为你的想法是对的。

不过,我认为您不需要所有复杂性。想象一下 JavaScript 是

if (veryExpensiveFunction()) {
  doThis();
} else {
  doThat();
}

现在假设veryExpensiveFunction 已实现。

function veryExpensiveFunction() {
   return true || evenMoreExpensiveFunction();
}

如果 JavaScript 因为 || 是惰性的(仅在需要时评估第二个参数),这将很快返回(因为 true 是,嗯,真的!)。如果它被实施,而不是像

function veryExpensiveFunction() {
  var a = true;
  var b = evenMoreExpensiveFunction(); // forces the evaluation

  return a || b;
}

这需要很长时间才能评估,因为您不必要地评估了参数。现在想象一下,应用于|| 的魔法被应用于每个函数(即惰性语言),您可能可以想象惰性可能带来的性能优势。

在你的例子中

function tak(x,y,z){ return (x <= y) ? y : tak(tak(x-1, y, z), tak(y-1, z, x), tak(z-1, x, y)); }

让我们假设一切都是懒惰的。

 var a = tak(1,2,3);

这不会做任何事情(无需评估)。 a 没有被使用,所以没有被评估。

 var a = tak(1,2,3);
 console.log(a);

现在我们可以推动对tak 的评估。在评估我们需要得到结果时,我们首先替换其中的值(用它们的参数替换变量)。然后我们评估条件,然后只评估我们需要的条件。

 console.log((1 <= 2) ? 2 : tak(tak(1-1, 2, 3), tak(2-1, 1, 3), tak(3-1, 1, 2));
 console.log(   true  ? 2 : tak(tak(1-1, 2, 3), tak(2-1, 1, 3), tak(3-1, 1, 2));
 console.log(2);

在这个例子中(假设我没有犯任何可怕的拼写错误),那么我们不需要评估除参数之外的任何其他内容,并且不会进行递归调用。

【讨论】:

  • 谢谢@Jeff Foster。没注意||到目前为止很懒惰,所以信息量很大。但是,这种技术不能应用于递归函数,因为我们有 pass (args) 来调用该函数并且它会立即在那里进行评估,对吗?
  • 没有。它仅根据需要进行评估。为了推动评估,有人必须询问递归函数的结果。
  • 嗯,我还是不太清楚。是否可以使用惰性 || 为 tak 函数编写代码?技术,如果你不介意的话。
  • tak(15,5,0) 在我的环境中需要 30-60 秒。惰性求值语言,例如 Haskell 或使用惰性求值技术修改的语言应该是即时的。
  • 我鼓励你在笔+纸上完成一个例子,只在你真正需要的时候评估它们。执行这种“按需调用”策略,您会发现您通常不需要评估对 tak 的调用,因为它们没有被使用。
猜你喜欢
  • 2011-08-01
  • 2011-02-14
  • 2011-12-23
  • 2016-06-13
  • 1970-01-01
  • 2010-10-20
  • 2011-03-21
  • 2019-03-14
  • 1970-01-01
相关资源
最近更新 更多