我是靠谱客的博主 迷路水杯,这篇文章主要介绍Prepack——让JavaScript代码运行更快速,现在分享给大家,希望可以做个参考。

原文:https://prepack.io/
翻译:Aladdin

Prepack是一个JavaScript源代码优化工具:实际上它是一个JavaScript的部分求值器(Partial Evaluator),可在编译时执行原本在运行时的计算过程,并通过重写JavaScript代码来提高其执行效率。Prepack用简单的赋值序列来等效替换JavaScript代码包中的全局代码,从而消除了中间计算过程以及对象分配的操作。对于重初始化的代码,Prepack可以有效缓存JavaScript解析的结果,使得优化效果最佳。

Prepack目前还处于开发初期,尚未完全投入生产使用。请诸位尝试并提供反馈,帮助我们修复bug。

入门:https://prepack.io/getting-started.html

试用:https://prepack.io/repl.html

几个优化小例子

Hello,World

复制代码
1
2
3
4
5
6
优化前: (function () { function hello() { return 'hello'; } function world() { return 'world'; } global.s = hello() + ' ' + world(); })();
复制代码
1
2
3
4
优化后: (function () { s = "hello world"; })();

消除抽象税

复制代码
1
2
3
4
5
6
7
8
9
优化前: (function () { var self = this; ['A', 'B', 42].forEach(function(x) { var name = '_' + x.toString()[0].toLowerCase(); var y = parseInt(x); self[name] = y ? y : x; }); })();
复制代码
1
2
3
4
5
6
优化后: (function () { _a = "A"; _b = "B"; _4 = 42; })();

斐波那契数列

复制代码
1
2
3
4
5
6
7
优化前: (function () { function fibonacci(x) { return x <= 1 ? x : fibonacci(x - 1) + fibonacci(x - 2); } global.x = fibonacci(23); })();
复制代码
1
2
3
4
优化后: (function () { x = 28657; })();

模块初始化

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
优化前: (function () { let moduleTable = {}; function define(id, f) { moduleTable[id] = f; } function require(id) { let x = moduleTable[id]; return x instanceof Function ? (moduleTable[id] = x()) : x; } global.require = require; define("one", function() { return 1; }); define("two", function() { return require("one") + require("one"); }); define("three", function() { return require("two") + require("one"); }); define("four", function() { return require("three") + require("one"); }); })(); three = require("three");
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
优化后: (function () { function _2() { return 3 + 1; } var _1 = { one: 1, two: 2, three: 3, four: _2 }; function _0(id) { let x = _1[id]; return x instanceof Function ? _1[id] = x() : x; } require = _0; three = 3; })();

请注意大多数计算是如何预先初始化的。然而,计算four( _2)的函数保留在剩余程序中,因为它在初始化时没有被调用。

环境相互作用和分支

复制代码
1
2
3
4
5
6
7
优化前: (function(){ function fib(x) { return x <= 1 ? x : fib(x - 1) + fib(x - 2); } let x = Date.now(); if (x === 0) x = fib(10); global.result = x; })();
复制代码
1
2
3
4
5
6
7
8
优化后: (function () { var _0 = Date.now(); if (typeof _0 !== "number") { throw new Error("Prepack model invariant violation"); } result = _0 === 0 ? 55 : _0; })();

它是如何工作的?

以下五个概念可以帮助你更好地理解Prepack的运行机制:
- 抽象语法树(AST)

Prepack在AST级别运行,使用Babel解析并生成JavaScript源代码。

  • 具体执行(Concrete Execution)

    Prepack的核心是一个JavaScript解释器,它与ECMAScript 5几乎完全兼容,而且紧密地保持与ECMAScript 2016语言规范的一致性,你可以将Prepack中的解释器视为完全参照JavaScript实现的。

    解释器能够跟踪并撤销包括所有对象Mutation在内的结果,从而能够进行推测优化(Speculative Optimization)。

  • 符号执行(Symbolic Execution)
    除了对具体值进行计算外,Prepack的解释器还可以操作受环境相互作用影响的抽象值。例如Date.now可以返回一个抽象值,你可以通过helper辅助函数(如__abstract())手动注入抽象值。Prepack会跟踪所有在抽象值上执行的操作,在遇到分支时,Prepack会执行并探索所有可能性。所以,Prepack实现了一套JavaScript的符号执行引擎。

  • 抽象释义(Abstract Interpretation)

    符号执行在遇到抽象值的分支时会分叉(fork),Prepack会在控制流合并点加入分歧执行(Diverged Execution)来实现抽象释义的形式。连接变量和堆属性可能会得到条件抽象值,Prepack会跟踪有关抽象值和型域(Type Domain)的信息。

  • 堆序列化(Heap Serialization)

    当全局代码返回,初始化阶段结束时,Prepack捕获最终的堆并按顺序排列堆栈,生成直观的JavaScript新代码,创建并链接初始化堆中可访问的所有对象。堆中的一些值可能是抽象值的计算结果,对于这些值,Prepack将生成原始程序完成计算所执行的代码。

环境很重要!

开箱即用,Prepack并没有完全建模浏览器或node.js环境:Prepack没有内置documentwindow。实际上,在编写引用这些属性的代码的时候,它们会评估undefined。您将必须在要优化的代码的开头插入相关功能模块。

以下帮助函数有助于编写功能模块。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Assume that a certain property has a simple known value. __assumeDataProperty(global, "obscure", undefined); // Assume that a certain property has a simple unknown value. __assumeDataProperty(global, "notSoObscure", __abstract()); // Assume that a richly structured value exists __assumeDataProperty(global, "rich", __abstract({ x: __abstract("number"), y: __abstract("boolean"), z: __abstract("string"), nested: __abstract({ x: __abstract() }) })); // Forbid any accesses to an object except at known positions __makePartial(global); // At this point, accessing global.obscure, global.notSoObscure, global.rich.nested.x is okay, // but accessing global.unknown or global.rich.unknown would cause an introspection error. // The following tells Prepack to embed and call some code in the residual program. // The code must not have any side effects on the reachable JavaScript heap. __residual("object", function(delay) { return global.pushSelfDestructButton(delay); }, "5 minutes");

未来规划

短期

  • 稳定现有功能集,用于预优化(Prepack)React Native代码包
  • 集成React Native工具链
  • 根据React Native使用的模块系统的假设来构建优化

中期

  • 进一步优化序列化(Serialization),包括
    • 消除不暴露identity的对象,
    • 消除未使用的导出属性,
  • 预优化每个函数、基本代码块、语句、表达式
  • 与ES6保持完全一致
  • 支持广泛的模块系统
  • 假设ES6支持某些功能,延迟完成或直接忽略Polyfill应用
  • 进一步实现Web和Node.js环境中的兼容性目标
  • 深入集成JavaScript虚拟机,改进堆反序列化过程,包括
    • 暴露“对象懒初始化”的概念 - 以一种JavaScript无感知的方式,在首次使用对象时对其进行初始化
    • 通过专门的字节码对普通对象的创建进行高效编码
    • 将代码分为两个阶段:1) 非环境依赖阶段,虚拟机可以安全地捕获并恢复生成的堆;2)环境依赖阶段,通过从环境中获得的值执行所有剩余的计算过程来拼凑具体的堆
  • 总结循环和递归

长期 - 利用Prepack作为平台

  • JavaScript Playground - 通过调整JavaScript引擎体验JavaScript特性,这些引擎由JavaScript所编写,托管在浏览器中;你可以把它想象成一个“Babel虚拟机”,实现了不能被编译的JavaScript新特性
  • 错误查找 - 查找异常崩溃,执行性能问题…
  • 效果分析,例如用于检测模块工厂功能的可能副作用或强制纯净注释
  • 类型分析
  • 信息流分析
  • 调用图推理,允许内联和代码索引
  • 自动测试生成,利用符号执行的特性与约束求解器(Constraint Solver)结合来计算执行不同执行路径的输入
  • 智能模糊(Smart Fuzzing)
  • JavaScript沙盒 - 以不可观察的方式有效地测试JavaScript代码

相关技术

Closure Compiler也能压缩优化JavaScript代码。而Prepack则在运行全局代码时,通过展开循环和递归的方式来完成代码的初始化。另外,Prepack专注于优化后代码运行时的性能,而Closure Compiler则侧重于压缩后JavaScript代码的大小。

最后

以上就是迷路水杯最近收集整理的关于Prepack——让JavaScript代码运行更快速的全部内容,更多相关Prepack——让JavaScript代码运行更快速内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(105)

评论列表共有 0 条评论

立即
投稿
返回
顶部