zl程序教程

您现在的位置是:首页 >  大数据

当前栏目

如何百倍加速 Lo-Dash?引入惰性计算

计算 如何 加速 引入
2023-09-14 09:02:12 时间

原文:How to Speed Up Lo-Dash ×100? Introducing Lazy Evaluation.
作者: Filip Zawada

译文:如何百倍加速 Lo-Dash?引入惰性计算
译者:justjavac

我一直以为像 Lo-Dash 这样的库已经不能再快了,毕竟它们已经足够快了。
Lo-Dash 几乎完全混合了各种 JavaScript 奇技淫巧(YouTube)来压榨出最好的性能。

但似乎我错了 - 其实 Lo-Dash 可以运行的更快。
你需要做的是,停止思考那些细微的优化,并开始找出更加适用的算法。
例如,在一个典型的循环中,我们往往倾向于去优化单次迭代的时间:

var len = getLength();

for(var i = 0; i len; i++) {

 operation(); // - 10毫秒 - 如何优化到9毫秒?!

}

代码说明:取得数组的长度,然后重复执行 N 遍 operation() 函数。译注 by @justjavac

但是,这(优化 operation() 执行时间)往往很难,而且对性能提升也非常有限。
相反,在某些情况下,我们可以优化 getLength() 函数。
它返回的数字越小,则每个 10 毫秒循环的执行次数就越少。

这就是 Lo-Dash 使用惰性计算的思想。
这是减少周期数,而不是减少每个周期的执行时间。
让我们看看下面的例子:

function priceLt(x) {

 return function(item) { return item.price };

var gems = [

 { name: Sunstone, price: 4 },

 { name: Amethyst, price: 15 },

 { name: Prehnite, price: 20 },

 { name: Sugilite, price: 7 },

 { name: Diopside, price: 3 }, 

 { name: Feldspar, price: 13 },

 { name: Dioptase, price: 2 }, 

 { name: Sapphire, price: 20 }

var chosen = _(gems).filter(priceLt(10)).take(3).value();

代码说明:gems 保存了 8 个对象,名字和价格。priceLt(x) 函数返回价格低于 x 的所有元素。译注 by @justjavac

我们把价格低于 10 美元的前 3 个 gems 找出来。
常规 Lo-Dash 方法(严格计算)是过滤所有 8 个 gems,然后返回过滤结果的前 3 个。

Lodash naïve approach

不难看出来,这种算法是不明智的。
它处理了所有的 8 个元素,而实际上我们只需要读取其中的 5 个元素就能得到我们想要的结果。
与此相反,使用惰性计算算法,只需要处理能得到结果的最少数量就可以了。
如图所示:

Lo-Dash regular approach

我们轻而易举就获得了 37.5% 的性能提升。
但是这还不是全部,其实很容易找到能获得 1000 倍以上性能提升的例子。
让我们一起来看看:

// 99,999 张照片

var phoneNumbers = [5554445555, 1424445656, 5554443333, … ×99,999];

// 返回包含 "55" 的照片

function contains55(str) {

 return str.contains("55"); 

// 取 100 张包含 "55" 的照片

var r = _(phoneNumbers).map(String).filter(contains55).take(100);

在这个例子中,map 和 filter 用来处理 99,999 个元素。
不过我们只需要它的一个子集就可以得到想要的结果了,例如 10,000 个,
性能提升也是非常大的(基准测试):

benchmark

Pipelining

惰性计算带来了另一个好处,我称之为 "Pipelining"。
它可以避免链式方法执行期间创建中间数组。
取而代之,我们在单个元素上执行所有操作。
所以,下面的代码:

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

将大致翻译为如下的常规 Lo-Dash(严格计算)

var result = [], temp1 = [], temp2 = [], temp3 = [];

for(var i = 0; i source.length; i++) {

 temp1[i] = func1(source[i]);

for(i = 0; i source.length; i++) {

 temp2[i] = func2(temp1[i]);

for(i = 0; i source.length; i++) {

 temp3[i] = func3(temp2[i]);

result = temp3;

如果我们使用惰性计算,它会像下面这样执行:

var result = [];

for(var i = 0; i source.length; i++) {

 result[i] = func3(func2(func1(source[i])));

}

不使用临时数组可以给我们带来非常显著的性能提升,特别是当源数组非常大时,内存访问是昂贵的资源。

和惰性计算一起使用的是延迟执行。
当你创建一个链,我们并不立即计算它的值,直到 .value() 被显式或者隐式地调用。
这种方法有助于先准备一个查询,随后我们使用最新的数据来执行它。

var wallet = _(assets).filter(ownedBy(me))

 .pluck(value)

 .reduce(sum);

$json.get("/new/assets").success(function(data) {

 assets.push.apply(assets, data); // 更新我的资金

 wallet.value(); // 返回我钱包的最新的总额

});

在某些情况下,这样做也可以加速执行时间。我们可以在前期创建复杂的查询,然后当时机成熟时再执行它。

Wrap up

懒惰计算并不是行业里的新理念。它已经包含在了许多库里面,例如 LINQLazy.js 等等。我相信 Lo-Dash 和这些库最主要的区别是,你可以在一个更新的、更强大的引擎里面使用原有的 Underscore API。不需要学习新的库,不需要修改代码,只是简单升级。

但是,即使你不打算使用 Lo-Dash,我希望这篇文章启发了你。
现在,当你发现你的应用程序存在性能瓶颈,不要仅仅是去 jsperf.com 以 try/fail 风格优化它。
而是去喝杯咖啡,并开始考虑算法。
最重要的是创意,但良好的数学背景会让你如鱼得水(book)。祝你好运!


阿里云函数计算——优势 阿里云函数计算——优势自制脑图, 函数计算构建业务系统,用户不再需要考虑服务器相关的问题,由此获得了相当明显的优势。
Laravel 8 新特性之:迁移压缩、任务批处理、速率限制优化 Laravel 8 通过引入 Laravel Jetstream,模型工厂类,迁移压缩,队列批处理,改善速率限制,队列改进,动态 Blade 组件,Tailwind 分页视图, 时间测试助手,artisan serve 的改进,事件监听器的改进,以及各种其他错误修复和可用性改进,对 Laravel 7.x 继续进行了改善。
排序优化:如何实现一个通用的、高性能的排序函数? 如何选择合适的排序算法? 如果要实现一个通用的、高效率的排序函数,我们应该选择哪种排序算法?我们先回顾一下前面讲过的几种排序算法。
蚂蚁图计算正式升级为TuGraph,查询效率提升10倍!兼容性更强 蚂蚁集团“大规模图计算系统GeaGraph”正式升级为TuGraph ,并完成了产品3.0版本的迭代。迭代后的版本查询效率提升10倍,兼容性更强。
速度高达百万帧/秒,颜水成团队开源RL环境并行模拟器,大幅节省CPU资源 在强化学习(RL)智能体模拟训练中,环境高速并行执行引擎至关重要。最近,新加坡 Sea AI Lab 颜水成团队提出一个全新的环境模拟并行部件 EnvPool,该部件在不同的硬件评测上都达到了优异的性能。
推理速度提升29倍,参数少1/10,阿里提出AdaBERT压缩方法 作为当前最佳的自然语言处理模型,BERT 却存在规模大、成本高和实时性差等缺点。为了能在实际应用中部署这种技术,有必要对 BERT 进行压缩。此前机器之心就已经介绍了几种来自不同研究机构的压缩方案,参阅《内存用量 1/20,速度加快 80 倍,腾讯 QQ 提出全新 BERT 蒸馏框架,未来将开源》和《AAAI 2020 | 超低精度量化 BERT,UC 伯克利提出用二阶信息压缩神经网络》。
迷渡 互联网打杂工。曾翻译《JavaScript Quirks》,正在出版《代码之谜》。JSON API 中文规范维护者,Flarum 中文社区创始人。平时混迹于 GitHub,参与众多开源项目,Star 数位列全球前 100 名。