- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在将语言的编译器优化为JavaScript,并发现了一个非常有趣的情况,即使没有令人沮丧的情况:
function add(n,m) {
return n === 0 ? m : add(n - 1, m) + 1;
};
var s = 0;
for (var i = 0; i < 100000; ++i) {
s += add(4000, 4000);
}
console.log(s);
它需要
2.3s
在我的机器上完成[1]。但是,如果我做一个很小的更改:
function add(n,m) {
return (() => n === 0 ? m : add(n - 1, m) + 1)();
};
var s = 0;
for (var i = 0; i < 100000; ++i) {
s += add(4000, 4000);
}
console.log(s);
它以
1.1s
完成。注意,唯一的区别是在
(() => ...)()
的返回周围添加了立即调用的lambda
add
。
为什么添加的调用使我的程序快两倍?
最佳答案
有趣的!通过查看代码,可以很明显地看出,用IIFE封装的版本应该更慢,而不是更快:在每次循环迭代中,它都会创建一个新的函数对象并调用它(优化编译器最终会避免,但是这样做不会) t马上开始),因此通常只会做更多的工作,这应该花费更多的时间。
这种情况下的解释是内联的。
有一点背景知识:将一个函数内联到另一个函数中(而不是调用它)是优化编译器执行以获得更好性能的标准技巧之一。但是,这是一把双刃剑:从正面来看,它避免了调用开销,并且通常可以实现进一步的优化,例如恒定传播或消除重复计算(请参见下面的示例)。不利的一面是,它会使编译花费更长的时间(因为编译器会做更多的工作),并且会导致生成更多的代码并将其存储在内存中(因为内联函数有效地复制了它),并且使用了动态语言(例如JavaScript),优化的代码通常依赖于 protected 假设,这增加了这些假设之一被证明是错误的风险,因此大量的优化代码不得不被丢弃。
一般来说,做出完美的内联决策(不要太多,也不要太少)需要预测 future :预先知道执行代码的频率和参数。当然,这是不可能的,因此优化编译器会使用各种规则/“启发式”来猜测可能是一个相当不错的决定。
V8当前具有的一条规则是:不要内联递归调用。
这就是为什么在您的代码的简单版本中,add
不会内联到自身中。 IIFE版本本质上具有两个相互调用的函数,称为“相互递归”-事实证明,此简单技巧足以使V8的优化编译器傻瓜并使其避开其“请勿内联递归调用”规则。取而代之的是,它愉快地将未命名的lambda内联到add
中,并将add
插入未命名的lambda中,依此类推,直到其内联预算在大约30轮后用完。 (附带说明:“内联多少”是一种较为复杂的启发式方法,尤其要考虑到函数的大小,因此,我们在此处看到的任何特定行为的确针对此情况。)
在这种特殊情况下,所涉及的函数非常小,内联会很大程度地帮助您,因为它避免了调用开销。因此,在这种情况下,即使是(伪装的)递归内联的情况,内联也可以提供更好的性能,这通常对性能不利。而且确实要付出代价:在简单版本中,优化编译器仅花费3毫秒的时间来编译add
,从而为其生成562字节的优化代码。在IIFE版本中,编译器花费30毫秒的时间,并为add
生成4318字节的优化代码。这就是为什么它不像得出结论“V8应该总是内联更多”那样简单的原因:编译所需的时间和电池消耗,内存消耗也很重要,以及在简单的10行中可以接受的成本(并显着提高性能)在10万行的应用程序中,demo的成本(甚至可能会降低整体性能)是 Not Acceptable 。
现在,了解了所发生的情况之后,我们可以回到“IIFE有开销”的直觉,并制定一个更快的版本:
function add(n,m) {
return add_inner(n, m);
};
function add_inner(n, m) {
return n === 0 ? m : add(n - 1, m) + 1;
}
在我的机器上,我看到:
add(n, m)
简单地实现为
return n + m
,则它会在2毫秒后终止-算法优化胜过了优化编译器可能完成的所有工作:-)
function Process(x) {
return (x ** 2) + InternalDetail(x, 0, 2);
}
function InternalDetail(x, offset, power) {
return (x + offset) ** power;
}
(显然,这是愚蠢的代码;但是让我们假设它是在实践中有意义的简化版本。)
temp1 = (x ** 2)
InternalDetail
,x
,0
2
temp2 = (x + 0)
temp3 = temp2 ** 2
temp3
返回给调用者temp4 = temp1 + temp3
temp4
。 function Process_after_inlining(x) {
return (x ** 2) + ( (x + 0) ** 2 );
}
这可以进行两种简化:
x + 0
可以折叠为
x
,然后
x ** 2
计算发生两次,因此第二次出现可以通过重用第一个出现的结果来代替:
function Process_with_optimizations(x) {
let temp1 = x ** 2;
return temp1 + temp1;
}
因此,与朴素的执行相比,我们从7个步骤减少了3个步骤:
temp1 = (x ** 2)
temp2 = temp1 + temp1
temp2
x + 0
始终是数字的情况下,在JavaScript中也不总是只能用
x
替换
x
的情况:如果
x
恰好是
-0
,则向其中添加
0
它到
+0
,这很可能是可观察到的程序行为;-)
关于javascript - 为什么添加立即调用的lambda会使我的JavaScript代码快2倍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65042502/
我希望在我的应用程序下载信息时显示 Toast 消息,但即使我将它放在我的代码之前,它也不会在下载完成后出现。将我的代码放在一个单独的线程中会引起很多麻烦,但是将 toast 放在一个单独的线程中也不
面临即时应用更新模式的问题。成功完成应用程序更新后,一切都关闭并且不重新启动应用程序。这就是问题所在。 但是android文档说: A full screen user experience that
我有一张 table 我有一个 anchor 标记,
我正在开发一个具有两个线程的 Java/Seam/Hibernate/Glassfish 应用程序: 线程 #1 发送各种消息并将摘要信息写入 MySQL 数据库。 线程 #2 定期轮询数据库(由 S
我找不到规范的相关部分来回答这个问题。在 Java 中的条件运算符语句中,是否同时评估真假参数? 以下是否会抛出 NullPointerException Integer test = null; t
大家下午好, 我想知道是否有办法使类的静态 block 运行,即使类本身没有被引用? 我知道它是延迟加载的,因此只需调用该类的任何函数即可开始启动该类, 但是,我希望该类在任何调用之前启动,换句话说,
我正在尝试使用 jQuery prop() 方法禁用元素(表单字段)。有两个字段,一个叫做fee,一个叫做currency。每当 fee 设置为 0 时,第二个字段 currency 将被禁用。这样做
我想为 UIButton 的缩放设置动画,并在完成后再次为缩放设置动画,但让它在没有动画的情况下旋转。我尝试将旋转变换放在没有持续时间的动画调用中,但不知何故它仍然成为缩放动画的一部分或替换缩放动画。
在 js 代码中,我创建了 3 个按钮 --- 按钮 1...按钮 2...按钮 3和 3 个输入字段 --- 输入字段 1...输入字段 2...输入字段 3 从脚本开始所有按钮都被禁用 只有当输入
我正在使用一个 threading.Thread() 来完成它的工作并返回 。它的返回记录在打印语句中,所以我确信有时候是这样的。然而,依靠 threading.active_count() 和 th
我正在使用 IntelliJ 9,我很好奇是否有任何与 Visual Studio“即时”调试窗口等效的 IntelliJ。在编辑器中选择所需的表达式,然后 ALT-F8 来评估表达式,但我希望能够在
我有一个两个标签页,一个标签是记录列表,点击记录会切换到编辑标签,编辑标签中有保存和取消按钮。 现在,我单击记录 #1,进行一些编辑,然后单击取消按钮。当然我不想验证表单,因为它被取消了,所以我设置了
我有一个 A viewController,首先,我呈现 B viewController,经过一些工作后,我需要关闭 B viewController 并呈现 C viewController,所以
我希望能够在文本框中输入内容,当用户在文本框中输入内容时,我希望程序无需单击按钮即可自动读取文本框。 例子:用户类型:“abcd”当用户输入时,程序会显示每个字母对应的数字。 程序输出:“1234”
如果任何表单输入发生更改,如何立即更改提交按钮文本? //This applies to whole form $('#test').change(function() { $("#send").
主要功能: var interval; function refreshId(sessio
假设我有一个包含这些列的 data.table nodeID hour1aaa hour1bbb hour1ccc hour2aaa hour2bbb hour2ccc .
根据vimeo js-api doc ,事件 finish - 当视频播放结束时触发。 出于某种原因,我无法让它工作,finish 事件总是立即调用,我做错了什么吗? 我试图让嵌入的视频在播放完毕后消
我想滑动当前ul元素下的所有li元素和slideDown li元素 $(document).ready(function(){ $("li").slideUp(); }); $(".nav")
我有一个表-compositeView,其中有行-itemView。每行都有许多事件 - 单击、更改等等。 在某些状态下,我想“锁定”表。禁用按钮并取消事件。 是否有一种好方法可以立即取消 itemv
我是一名优秀的程序员,十分优秀!