优化 JS 程序的一个小方法

文/ 阿里淘系 F(x) Team - 甄子

就像在学习之前先要识字,我想在介绍优化 JavaScript 代码之前,先介绍一下自己对编程语言的理解。故事要从一直叫做 Theseus 的机械鼠和其发明人克劳德-香农(Claude Shannon)说起。

在传记《A Mind at Play:How Claude Shannon Invented the Information Age》中,作者 Jimmy Soni 和 Rob Goodman 强烈希望将香农的作品 Theseus 展示给广大读者。面对复杂的迷宫,Theseus 仅用一堆继电器、ROM 存储等简单而古老的电子元器件,就完成了对复杂迷宫的探索和成功线路的记忆,第二次沿着正确道路走出迷宫的 Theseus 没犯一点儿错误。大多数人认为这不过是骗人的把戏和小玩意儿,弃之如敝履。

少数聪明人眼里 Theseus 蕴含的惊人智慧简直可以和牛顿、爱因斯坦媲美,香农凭借一己之力将布尔代数引入电子电路设计启发了后世数字电路乃至计算机的发明。

数字电路和程序的关系

图 4-44 我在 2011 年做的智能调光调色电路

如图 4-44 就是一个在香农启发下产生的数字电路,Theseus 中古老的电子元器件已经变成了右侧红框的集成电路。通过将两个电容和一个晶振组成左侧红框的振荡电路,给集成电路提供时钟。集成电路上有数模转换电路,把高低电平代表的布尔代数运算电路计算结果转换成模拟信号输出,可调光调色的 LED 模组上就产生了功率输出的不同:颜色、频率输出的不同:亮度。在香农的启发和帮助下,图 4-44 的电路实现了一个完整的程序功能:随时钟变化不断改变 LED 颜色和亮度。

如果图 4-44 的完整程序直接通过翻查集成电路手册,按照图 4-45 对照引脚定义和手册里对寄存器*作的地址写入对应的指令和数据。

图 4-45 我的电路中用到的集成电路手册信息(摘录自 ATMEL 官网)

从这个例子中可以看到,在香农的启发和帮助下,数字电路最大的好处就是把电路进行了抽象,让程序逻辑和数字电路在这个抽象层面上统一起来。在这个新的统一抽象层面上,程序逻辑的控制可以类比为逻辑电路中的控制,程序的输入输出可以类比为数字电路中的存储(ROM、RAM)。时钟电路给数字电路中信号传输提供标尺,中央处理器根据这些标尺来控制数字电路中信号的传输和流转,这种传输和流转则把点状的控制变成控制流,把点状的存储变成数据流。因此,在程序中最重要的是控制流和数据流。

为了不必每次都查手册用引脚去烧写 ROM 为数字电路注入控制指令和数据,前辈们发明了一套烧写**,用汇编或 C 语言来定义和描述控制流和数据流,再由编译器翻译成图 4-45 对应的寄存器地址和指令、数据,然后通过烧写器变成数字信号通过集成电路引脚传输到集成电路内部完成程序的烧写。这套**让我们摆脱手册(当然不是完全摆脱,有时候还是要查但频率大幅降低)直接用变成语言去描述程序,再通过模拟器(类似于前端 MOCK 数据)完成调试和模拟测试,让我们对数字电路编程变得异常简单。

图 4-46 对数字电路进行编程(摘自 ATMEL 官网)

如图 4-46 这种方式控制数字电路就容易多了,您可以在淘宝买一个 Arduino 开发板,然后按照下面的代码自己试试从本质上理解程序是什么?数字电路是什么?计算的本质是什么?

unsigned long colorT[] = { 0xff3300,0xff3800,0xff4500,0xff4700,0xff5200,0xff5300,0xff5d00,0xff5d00,0xff6600,0xff6500, 0xff6f00,0xff6d00,0xff7600,0xff7300,0xff7c00,0xff7900,0xff8200,0xff7e00,0xff8700,0xff8300, 可以自己继续添加 } int R_Pin = 11; int G_Pin = 10; int B_Pin = 9; // 这里就是手册中集成电路输出信号的引脚和 LED 模块连接方式对应 int red,green,blue = 0; int i = 0; int l = sizeof(colorT); void setup(){ pinMode(12, OUTPUT); pinMode(R_Pin, OUTPUT); pinMode(G_Pin, OUTPUT); pinMode(B_Pin, OUTPUT); digitalWrite(12, LOW); } void setColor(int redValue, int greenValue, int blueValue){ ****ogWrite(R_Pin, redValue); ****ogWrite(G_Pin, greenValue); ****ogWrite(B_Pin, blueValue); } void loop(){ red = (colorT[i] >> 16) & 0xff; green = (colorT[i] >> 8) & 0xff; blue = (colorT[i] >> 0) & 0xff; setColor(red, green, blue); i++; if(i >= l){ i = 0; } delay(200); // 控制时钟信号 }

神游了一圈儿,接下来我们来看看如何观察 JavaScript 的控制流和数据流。前面提到要用 parser 对原始的代码文本(字符串)进行处理,最常见的处理目的是生成“抽象语法树”(AST)。当然,对于 D2C Schema 和 DesignToken 的 parsing 是为了输出正确的、内联 CSS 的完整 HTML 文档。​

抽象语法树 AST

之所以要把 JavaScript 代码文本转换成 AST 是因为编译器无法对字符串构成的程序文本进行直接*作,只有把程序文本从“1+2”变成new BinaryExpression(ADD, new Number(1), new Number(2))这种形式,才能被编译器理解。怎么样?是不是和 ATMEL 集成电路的编程很像?*作ADD和数据new Number(1)。因此,从程序文本到 AST 的过程可以类比成**过程 parsing,用于**的那段儿代码就叫做 parser。知道了这些,我们就可以把程序文本也就是经常挂在嘴边的“代码”翻来覆去的玩儿,代码文本变成 AST 推荐 esprima 这个工具,遍历 AST 节点并进行一些修修补补(优化性能?),最后把修改过的 AST 再转换成代码文本可以用 escodegen 。

// 生成 AST 抽象语法树 const esprima = require('esprima'); const AST = esprima.parseScript(jsCode); // 遍历和修改 AST const estraverse = require('estraverse'); const escodegen = require('escodegen'); function toEqual(node){ if(node.operator === '=='){ node.operator = '==='; } } function walkIn(ast){ estraverse.traverse(ast, { enter: (node) => { toEqual(node); } }); } // 从 AST 进行代码生成 const escodegen = require('escodegen'); const code = escodegen.generate(ast);

具备上面的技能后,让我们拿一段儿真实的代码来练练手。

acc = 0; i = 0; len = loadArrayLength(arr); loop { if (i >= tmp) break; acc += load(arr, i); i += 1; }

用 esprima 提供的 parser 把这段代码转换成 AST 后,我借助 GraphViz 工具把 AST 从 JSON 格式转换成 digraph 格式的 .gv 文件,然后生成图 4-47。

图 4-47 对 AST 进行可视化

数据流图 DFG

图 4-47 是一棵树,所以能很方便的进行遍历,当我们访问 AST 节点时生成对应的机器代码。这个方法的问题在于,关于变量的信息非常稀少,并分散在不同的树节点上。为了优化安全地将长度查找移出循环,我们需要知道数组长度不会在循环迭代之间变化。人类只需查看源代码即可轻松完成,但编译器需要做大量工作,才能自信地直接从 AST 中提取这些事实。与许多其他编译器问题一样,这通常通过将数据提升到更合适的抽象层,即中间表示(IR)来解决。在这个特定情况下,IR 的选择被称为数据流图(DFG)。与其谈论语法实体(如for loopexpressions、...),我们应该谈论数据本身(读取、变量值),以及它如何在程序中变化。

在我们的特定示例中,我们感兴趣的数据是变量arr的值。我们希望能够轻松观察它的所有使用,以验证没有越界访问或任何其他更改来修改数组的长度,这是我们优化的前提。通过引入不同数据值之间的“使用”(定义和使用)关系来实现的。具体而言,这意味着该值已声明一次(图 4-47 中的_节点_),并且它已用于创建新值(图 4-47 的_边_)。显然,将不同的值连接在一起将形成如图 4-48 的数据流图。

图 4-48 数据流图


注意数据流图 4-48 中的红色array框,离开它的实心箭头表示此值的用法。通过在这些边上迭代,编译器可以导出array的值用于:

  • loadArrayLength
  • checkIndex
  • load

如果以**性方式访问数组节点的值(即存储、长度大小),则此类图的构造方式是显式“**”数组节点。每当我们看到array节点并观察其用途时,总是确定它的值不会改变。这听起来可能很复杂但很容易实现,该数据流图遵循单一静态分配(SSA)规则。简而言之,要将任何程序转换为 SSA,编译器需要重命名变量的所有赋值和后续使用,以确保每个变量只分配一次。

例如,在SSA之前:

var a = 1; console.log(a); a = 2; console.log(a);

SSA之后:

var a0 = 1; console.log(a0); var a1 = 2; console.log(a1);

通过 SSA 后我们可以确定,当谈论a0时实际上是在谈论它的单个任务。​

控制流图 CFG

使用数据流分析来从程序中提取信息,使我们能够就如何优化它做出安全假设。这种数据流表示在许多情况下非常有用,唯一的问题是通过将代码转换为数据流图,在表示链(从源代码到机器代码)中与 AST 相比,这种中间表示更不适合生成机器代码。由于程序逻辑是一个顺序排列的指令列表,CPU 一个接一个地执行它,数据流图似乎没有传达这一点。通常,通过将图节点分组到块中解决这个问题,这个表示形式称为控制流程图(CFG)。

b0 { i0 = literal 0 i1 = literal 0 i3 = array i4 = jump ^b0 } b0 -> b1 b1 { i5 = ssa:phi ^b1 i0, i12 i6 = ssa:phi ^i5, i1, i14 i7 = loadArrayLength i3 i8 = cmp "<", i6, i7 i9 = if ^i6, i8 } b1 -> b2, b3 b2 { i10 = checkIndex ^b2, i3, i6 i11 = load ^i10, i3, i6 i12 = add i5, i11 i13 = literal 1 i14 = add i6, i13 i15 = jump ^b2 } b2 -> b1 b3 { i16 = exit ^b3 }

如图 4-49 所示,我们可以按照之前的方法把他编程一张控制流图。

图 4-49 控制流图


如您所见:块b0中的循环前有代码,b1中的循环头,b2中的循环测试,b3中的循环主体,b4中的退出节点。从这个例子翻译成机器代码非常容易,将iXX替换为CPU寄存器名称(就像前文在 ATMEL 手册上查到的寄存器地址),并为每个指令逐行生成机器代码。

CFG 具有数据流关系和顺序,这使我们能够将其用于数据流分析和机器代码生成。然而,试图通过*纵其中包含的块及其内容来优化 CFG,会变得复杂且容易出错。相反,Clifford Click 和 Keith D Cooper 提议使用一种叫做“节点海”的方法,来消除 CFG 和复杂的数据流图带来的麻烦。​

节点海 Node Sea

还记得带有虚线的花哨数据流图吗?这些虚线实际上是使该图成为节点海图的原因。我们选择将控制依赖项声明为图中的虚线边缘,而不是将节点分组并对其进行排序。如果我们删除所有未破线的东西,并稍微分组一些事情,我们将得到图 4-50 所示的节点海图。

图 4-50 节点海(Node Sea)

图 4-50 节点海是查看代码非常强大的方式,它具有一般数据流图的所有信息,无需不断删除/替换块中的节点即可轻松更改以实现优化。节点海图通常通过图约简进行修改,我们只需将图表中的所有节点排队,为队列中的每个节点调用我们的函数,此函数涉及的所有内容(更改、替换)都将放入另一个队列,稍后将传递给优化函数。如果您有许多优化点,例如:合并/减少网络请求、合并/减少 JSBridge 调用、合并/减少本地存储 API 调用等,您可以将它们堆叠在一起,并在队列中的每个节点上应用它们,如果它们依赖于彼此的最终状态,您也可以逐一应用它们。​


相关股票:
相关概念: 集成电路 换电 ETC

崛起的中科系,被改变的我国芯片产业格局

当前,以芯片为代表的信创产业逐步成为国家科技竞争力的重要标志。在国产CPU产业强势崛起的过程中,你首先想到的会是哪几企业?答案有很多,但“中科系”的提及率绝对很高。作为国家战略科技力量,“中科系”旗下

芯片战场丨芯片领域三箭齐发 英特尔跑步突围

21世纪经济报道记者倪雨晴 圣何塞报道在硅谷源泉之一的圣何塞,英特尔CEO帕特·基辛格(Pat Gelsinger)正在带领英特尔加速奔跑。当地时间9月19日,2023英特尔on技术创新大会于美国加利

OPPO重启芯片业务?国产芯片或需告别“单打独斗”

财联社9月19日讯(记者 唐植潇)近日有消息称,OPPO将会重启芯片业务,并且“有部分员工已经回流,加入到了车载业务之中”。记者就此事向OPPO方面进行核实,对方表示“不予置评”。特百惠(我国)数字与

600亿颗芯片!我国巨头正式宣布,美媒:**也没料到制裁这么快

我国芯片市场与美国依赖我国的集成电路市场一直以来都是一个巨大的市场,拥有庞大的需求和巨大的增长潜力。我国的电子消费市场一直在迅速增长,包括智能手机、电视、电脑和各种智能设备等,这些设备都需要高性能的芯

最新手机芯片天梯图:A17、华为麒麟9000S,排在什么位置?

近日,最火的两颗芯片分别是苹果的3nm芯片A17 Pro,虽然很多人吐槽它较上一代提升不明显,但论性能,可以碾压任何安卓芯片,甚至是领先2代的。另外一款芯片,则是华为麒麟9000S,当然,这颗芯片工艺

韩国芯片连续13个月暴跌,尹锡悦指责我国不采购,外媒:自食其果

据韩国媒体称,韩国的半导体出口额已经连续暴跌13个月了,比去年同比下降了28%左右。韩国政府急的焦头烂额。尹锡悦政府竟直接甩锅我国,话里话外都是指责,他认为韩国半导体卖不出竟是我国的原因,我国应该帮助

我国突破芯片瓶颈将影响全球秩序?美国很担心,指出我国关键弱点

我国在芯片半导体领域一直深受美国的**,通过贸易制裁的方式阻止高端芯片进入我国市场。这样的举措一度造成我国芯片领域发展断档,不过随着我国科技企业近几年的突破,目前我国已经在芯片制造方面取得了重大的成果

没有他,我国芯片发展至少要**十年?

前几天,华为一声不响的上线了mate60系列,带着麒麟芯片9000s强势回归,吸引了全世界的目光。而华为麒麟芯片**背后,我们不该忘记这位老人—张汝京。我国半导体之父,为回**造芯片,被开除**户籍,

陈清泰:未来汽车颠覆传统,50%以上的零部件体系面临重构

【有车以后 资讯】“未来汽车对传统汽车的颠覆性,使传统零部件体系的50%以上都面临重构。”12月16日,在全球智能汽车产业峰会(GIV2022)上,我国电动汽车百人会理事长陈清泰指出,智能汽车的价值链

「姿势」一辆汽车由多少个零件组成?保证你说不清...

投稿点这里汽车有多少个零件?其实这个问题并没有一个十分确切的标准答案...据估计,一般轿车约由1万多个不可拆解的**零部件组装而成。结构极其复杂的特制汽车,如F1赛车等,其**零部件的数量可达到2万个

全球最大的10家汽车零部件供应商 都是世界500强 无我国企业

【卡车之家 原创】美国《财富》**每年发布的世界500强排行榜,是以营业收入数据对全球企业作出排名的榜单。2017年“世界500强”榜单中,汽车制造商和零部件厂商共占据33席(除去大型工程车辆企业),

汽车零部件企业哪家强?除了博世**还有这些名字你一定耳熟能详

文:懂车帝原创 李德喆[懂车帝原创 行业]9月18日,由《我国汽车报》主办,罗兰贝格协办的2019汽车零部件“双百强”企业发布会在江苏南京举行。在两份榜单中,博世、**、电装位列2019全球汽车零部件

汽车零部件行业现状及产业链

行业现状(Reference:产业运行 | 2021年汽车工业经济运行情况)中汽协预测:2022年我国汽车销量达到2700万辆,新能源销量超过550万辆(Reference:乘用车市场信息联席会)以乘

全球十大汽车零部件供应商,核心技术都被他们垄断,自主遗憾缺席

提到电影,我们会想到张艺谋、冯小刚,而很少会想到幕后的制作人;提起流行乐,我们会想到周杰伦、萧敬腾,而很少会想到背后的作词人。台前台后,一幕之别,知名度往往相差甚远。车界又何尝不是如此,知名车企我们都

高清汽车各零部件构造图,看完你就是汽车专家!

2023世界移动通信大会即将举行,大批中企强势回归!

来源:环球时报 【环球时报记者 倪浩 陶震 环球时报驻德国特约记者 青木】经过3年疫情后,全球最具影响力的通信展今年有望再现往日盛况。2月27日至3月2日,由全球移动通信**协会(GSMA)主办的20

太空新赛道:6G时代的卫星通信,究竟是什么?

近日华为、苹果争相推出手机卫星通信功能,成为一大亮点,不少手机厂商也将目光投到卫星通信。放眼未来,手机直连卫星的卫星通信服务将是大势所趋,也是6G时代的重要标志。华为以“北斗三号”为依托,率先把“卫星

光纤#光纤通信

国内企业在光通信产品的参数测试过程中,通常使用国外的先进测试设备。然而,这些测试仪器之间往往是孤立存在的,需要手动调试仪器并通过旋钮、按钮和人眼观察波形或数据。这不仅*作繁琐易出错,而且测试效率低下。

龙头20cm涨停,7天股价翻倍!一文看懂卫星通信前世今生及产业链

卫星通信概念股华力创通今日再度强势拉升,截至发稿,该股股价20cm涨停,7个交易日累计涨幅近113%,现报23.52元续刷阶段新高,总市值155.9亿元。消息上,有媒体从供应链获悉,Mate 60 P

工信部:目前我国尚不具备实现网络层面的移动通信号码归属地变更的条件

针对网友提出的“电话号码归属地更改”建议,工信部近日给出了官方回复。此前,有网友在人民网留言板向工信部留言称,“现在电话都是实名制,电话号绑定的***及一些主流的软件较多,更换号码后造成一系列问题

AD
更多相关文章