「注释」作者在本文里没有说明这么一个事实:

目前的版本Lo-Dash v2.4.1并没有引入延迟求值的特性,Lo-Dash 3.0.0-pre 中部分方法进行了引入,比如filter(),map(),reverse()

原文

我时常觉得像Lo-Dash这样优秀的库已经无法再优化了。它整合了各种奇技淫巧已经将JavaScript的性能开发到了极限。它使用了最快速的语句,优化的算法,甚至还会在发版前做性能测试以保证回归没问题。

延迟求值

但似乎我错了-还可以让Lo-Dash有明显的提升。只需将关注点从细微的优化转移到算法上来。譬如,在一次循环中我们往往会去优化循环体:

  1. var len = getLength();
  2. for(var i = 0; i < len; i++) {
  3. operation(); // <- 10ms - 如何做到 9ms?!
  4. }

但针对循环体的优化往往很难,很多时候已经到极限了。相反,优化getLength() 函数尽量减少循环次数变得更有意义了。你想啊,这个数值越小,需要循环的10ms就越少。

这便是Lo-Dash实现延迟求值的大致思路。重要的是减少循环次数,而不是每次循环的时间。让我们考察下面的例子:

  1. function priceLt(x) {
  2. return function(item) { return item.price < x; };
  3. }
  4. var gems = [
  5. { name: 'Sunstone', price: 4 }, { name: 'Amethyst', price: 15 },
  6. { name: 'Prehnite', price: 20}, { name: 'Sugilite', price: 7 },
  7. { name: 'Diopside', price: 3 }, { name: 'Feldspar', price: 13 },
  8. { name: 'Dioptase', price: 2 }, { name: 'Sapphire', price: 20 }
  9. ];
  10. var chosen = _(gems).filter(priceLt(10)).take(3).value();

我们只想取出3个价格低于10元的小球。通常情况下我们先过滤整个数据源,最后从所有小于10的元素里返回前面三个即可。

但这种做法并不优雅。它处理了全部8个数据,但其实只需要处理前面5个我们就能拿到结果了。同样为了得到正确的结果,延迟求值则只处理最少的元素。优化后如下图所示:

一下子就获得了37.5%的性能提升。很容易找出提升X1000+的例子。比如:

  1. var phoneNumbers = [5554445555, 1424445656, 5554443333, ×99,999];
  2. // 取出100个含 `55` 的号码
  3. function contains55(str) {
  4. return str.contains("55");
  5. };
  6. var r = _(phoneNumbers).map(String).filter(contains55).take(100);

这个例子中mapfilter 将遍历99999 个元素,但很有可能我们只需处理到1000个元素的时候就已经拿到想要的结果了。这回性能的提升就太明显了(benchmark):

流水线

延迟求值同时带来了另一个好处,我称之为“流水线”。要旨就是避免产生中间数组,而是对一个元素一次性进行完所有操作。下面用代码说话:

  1. var result = _(source).map(func1).map(func2).map(func3).value();

上面看似优雅的写法在原始的Lo-Dash里会转换成下面的样子(直接求值):

  1. var result = [], temp1 = [], temp2 = [], temp3 = [];
  2. for(var i = 0; i < source.length; i++) {
  3. temp1[i] = func1(source[i]);
  4. }
  5. for(i = 0; i < source.length; i++) {
  6. temp2[i] = func2(temp1[i]);
  7. }
  8. for(i = 0; i < source.length; i++) {
  9. temp3[i] = func3(temp2[i]);
  10. }
  11. result = temp3;

当引入了延迟求值后,代码大致就成这样的了:

  1. var result = [];
  2. for(var i = 0; i < source.length; i++) {
  3. result[i] = func3(func2(func1(source[i])));
  4. }

减少不必要的中间变量多少会带来性能上的提升,特别是在数据源特别巨大,内存又吃紧的情况下。

延迟执行

延迟求值带来的另一个好处是延迟执行。无论何时你写了段链式代码,只有在显式地调用了.value()后才会真正执行。这样一来,在数据源需要异步去拉取的情况下,可以保证我们处理的是最新的数据。

  1. var wallet = _(assets).filter(ownedBy('me'))
  2. .pluck('value')
  3. .reduce(sum);
  4. $json.get("/new/assets").success(function(data) {
  5. assets.push.apply(assets, data); // 更新数据源
  6. wallet.value(); // 返回的结果是最新的
  7. });

而且这种机制在某些情况下也会提高执行效果。我们可以老早发送一个请求获取数据,然后指定一个精确的时间来执行。

后记

延迟求值并且不算什么新技术。在一些库中已经在使用了,比如LINQ,Lazy.js还有其他等等。那么问题来了,Lo-Dash存在的意义是啥?我想就是你仍然可以使用你熟悉的Underscore 接口但享受一个更高效的底层实现,不需要额外的学习成本,代码上面也不会有大的变动,只需稍加修改。

延迟求值-如何让Lo-Dash再提速x100?的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[匿名类型、Lambda、延迟求值和主动求值]

    前言 从.NET3.0开始,C#开始一直支持一个新特性:匿名类型.匿名类型由var.赋值运算符和一个非空初始值(或以new开头的初始化项)组成.匿名类型有如下基本特性: 1.既支持简单类型也支持复杂类 ...

  2. 编写高质量代码改善C#程序的157个建议——建议28:理解延迟求值和主动求值之间的区别

    建议28:理解延迟求值和主动求值之间的区别 要理解延迟求值(lazy evaluation)和主动求值(eager evaluation),先看个例子: List<, , , , , , , , ...

  3. c++11实现l延迟调用(惰性求值)

    惰性求值 惰性求值一般用于函数式编程语言中,在使用延迟求值的时候,表达式不在它被绑定到变量之后就立即求值,而是在后面的某个时候求值.     可以利用c++11中的std::function, lam ...

  4. 深入理解JavaScript系列(19):求值策略(Evaluation strategy)

    介绍 本章,我们将讲解在ECMAScript向函数function传递参数的策略. 计算机科学里对这种策略一般称为“evaluation strategy”(大叔注:有的人说翻译成求值策略,有的人翻译 ...

  5. 表达式求值(noip2015等价表达式)

    题目大意 给一个含字母a的表达式,求n个选项中表达式跟一开始那个等价的有哪些 做法 模拟一个多项式显然难以实现那么我们高兴的找一些素数代入表达式,再随便找一个素数做模表达式求值优先级表 - ( ) + ...

  6. 用Python3实现表达式求值

    一.题目描述 请用 python3 编写一个计算器的控制台程序,支持加减乘除.乘方.括号.小数点,运算符优先级为括号>乘方>乘除>加减,同级别运算按照从左向右的顺序计算. 二.输入描 ...

  7. 左求值表达式,堆栈,调试陷阱与ORM查询语言的设计

    1,表达式的求值顺序与堆栈结构 “表达式” 是程序语言一个很重要的术语,也是大家天天写的程序中很常见的东西,但是表达式的求值顺序一定是从左到右么? C/C++语言中没有明确规定表达式的运算顺序(从左到 ...

  8. C#函数式编程之惰性求值

    惰性求值 在开始介绍今天要讲的知识之前,我们想要理解严格求值策略和非严格求值策略之间的区别,这样我们才能够深有体会的明白为什么需要利用这个技术.首先需要说明的是C#语言小部分采用了非严格求值策略,大部 ...

  9. 数据结构--栈的应用(表达式求值 nyoj 35)

    题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=35 题目: 表达式求值 时间限制:3000 ms | 内存限制:65535 KB描述 AC ...

随机推荐

  1. Android总结之链式调用(方法链)

    前言: 最近在学习总结Android属性动画的时候,发现Android的属性动画设计采用了链式调用的方式,然后又回顾了一下了以前接触的开源框架Glide也是采用链式调用的方式,还有最近火的一塌糊涂的R ...

  2. 从啥也不会到可以胜任最基本的JavaWeb工作,推荐给新人的学习路线(二)

    在上一节中,主要阐述了JavaScript方面的学习路线.先列举一下我朋友的经历,他去过培训机构,说是4个月后月薪过万,虽然他现在还未达到这个指标. 培训机构一般的套路是这样:先教JavaSE,什么都 ...

  3. Android中点击事件的实现方式

    在之前博文中多次使用了点击事件的处理实现,有朋友就问了,发现了很多按钮的点击实现,但有很多博文中使用的实现方式有都不一样,到底是怎么回事.今天我们就汇总一下点击事件的实现方式. 点击事件的实现大致分为 ...

  4. 编译器开发系列--Ocelot语言1.抽象语法树

    从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树 ...

  5. PMON failed to acquire latch, see PMON dump

    前几天,一台Oracle数据库(Oracle Database 10g Release 10.2.0.4.0 - 64bit Production)监控出现"PMON failed to a ...

  6. Linux基础介绍【第六篇】

    定时任务crond介绍 crond是什么? crond是linux系统中用来定期执行命令或指定程序任务的一种服务或软件.一般情况下,安装完CentOS5/6 linux操作系统之后,默认便会启动cro ...

  7. 定时Job在IIS中潜在危险-IIS 定期回收

    引言 有时我们会在IIS中启用一些定时服务,但是你必须清楚IIS会定期回收Asp.net的应用程序的.首先来看IIS啥时候回收APPDomain.   APPDomain 回收时机 There are ...

  8. jetBrain系列软件

    请尽量支持正版软件!https://www.jetbrains.com/ 本文仅供参考 以下提供一种方法可以无限期体验JetBrain2016系列软件. 1.下载JetbrainsCrack-2.5. ...

  9. C#编写windows服务,多服务为什么只启动一个(ServiceBase.Run)

    https://zhidao.baidu.com/question/380395667.html //多服务一个宿主程序时必须注间以下要点: Service1的ServiceName 必须 Insta ...

  10. 【腾讯bugly干货分享】微信Android热补丁实践演进之路

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1264& ...