settimeout 性能_性能测试setTimeout(0)的奇怪案例

settimeout 性能

by Netta Bondy

由Netta Bondy

性能测试setTimeout(0)的奇怪案例 (The curious case of performance testing setTimeout(0))

(要获得完全效果,请在被烟云包围的同时以沙哑的声音朗读) ((For full effect, read in a husky voice while surrounded by a cloud of smoke))

It all began on a gray fall day. The sky was cloudy, the wind was blowing, and someone told me that setTimeout(0) creates, on average, a 4 ms delay. They claimed that’s the time it takes to pop the callback off the stack, onto the callback queue and back on the stack again. I thought it sounded fishy (this is the bit you imagine me in black and white with a cigar in my mouth). Given that the rendering pipeline needs to run every 16 ms to allow smooth animations, 4 ms seemed like a long time to me. A very long time.

一切始于灰色的秋天。 天空是多云的,风在吹,有人告诉我setTimeout(0)平均会产生4毫秒的延迟。 他们声称这是将回调从堆栈弹出,放到回调队列中并再次返回堆栈所花费的时间。 我以为听起来很腥(这是您想象中的黑白相间,嘴里有雪茄的味道)。 鉴于渲染管道需要每16 ms运行一次以允许流畅的动画,所以4 ms对我来说似乎很长一段时间。 很长一段时间。

A few naive tests in the devtools with console.time()confirmed it. The average delay across 20 runs was about 1.5 ms. Of course, 20 runs does not a sufficient sample size make, but now I had a point to prove. I wanted to run tests on a larger scale that could get me a more accurate answer. I could then, of course, go and wave that in my colleague’s face to prove that they were wrong.

devtools中通过console.time()进行的一些幼稚测试证实了这一点。 20次运行的平均延迟约为1.5毫秒。 当然,进行20次运行并不能获得足够的样本量,但是现在我有一点要证明。 我想进行更大范围的测试,以获得更准确的答案。 然后,我当然可以挥舞同事的脸,证明他们错了。

Why else do we do what we do?

我们为什么还要做什么?

传统方法 (The traditional method)

Right away, I found myself in hot water. In order to measure how long it took setTimeout(0) to run, I needed a function that:

马上,我发现自己陷入了热水。 为了测量setTimeout(0)运行多长时间,我需要一个函数:

  • took a snapshot of the current time

    拍摄了当前时间的快照
  • executed setTimeout

    执行setTimeout

  • then exited immediately so that the stack would be clear and the scheduled callback could run and calculate the time difference

    然后立即退出,以便清除堆栈,并可以运行计划的回调并计算时间差
  • and I needed that function to run a sufficiently large number of times so that the calculations were statistically meaningful

    我需要该函数运行足够多次,以便计算具有统计意义

But the go-to construct for this — the for-loop — wouldn’t work. Because the for-loop doesn’t clear the stack until it has executed every loop, the callback wouldn’t run immediately. Or, to put it in code, we would get this:

但是,为此而进行的构造(for循环)将不起作用。 因为for循环直到执行完每个循环才清除堆栈,所以回调不会立即运行。 或者,将其放入代码中,我们将得到以下信息:

The problem here was inherent — if I wanted to run setTimeout multiple times automatically, I would have to do it from within another context. But, so long as I ran from within another context, there would always be an additional delay from the time I started the test to the time the callback executed.

这里的问题是固有的-如果我想自动运行setTimeout多次,则必须在另一个上下文中执行。 但是,只要我从另一个上下文中运行,从我开始测试到执行回调之间总会有额外的延迟。

Of course I could slum it like some of these good-for-nothing detectives, write a function that does what I need, and then copy & paste it 10,000 times. I would learn what I wanted to know, but the execution would be far from graceful. If I was going to rub this in someone else’s face, I’d much rather do it another way.

当然,我可以像其中一些毫无用处的侦探一样贫民窟,编写一个满足我需要的功能,然后将其复制并粘贴10,000次。 我会学到我想知道的东西,但是执行起来远非优雅。 如果要在别人的脸上擦这个,我宁愿换另一种方式。

Then it came to me.

然后就来找我。

革命性的方法 (The revolutionary method)

I could use a web worker.

我可以使用网络工作者。

Web workers run on a different thread. So, if I place the setTimeout logic in a web worker I could call that multiple times.Each call would create its own execution context, calling setTimeout, and immediately exiting the function so the callback could execute. I had been looking forward to do some work with web workers.

Web worker在不同的线程上运行。 因此,如果我将setTimeout逻辑放置在Web Worker中,则可以多次调用该调用,每个调用都会创建自己的执行上下文,调用setTimeout ,然后立即退出该函数以执行回调。 我一直期待着与网络工作者一起工作。

It was time to switch to my trusted Sublime Text.

是时候切换到我信任的Sublime Text了

I started out just testing the waters. With this code in main.js:

我刚开始只是测试水域。 在main.js使用以下代码:

Some plumbing here to prep for the actual test, but initially I just wanted to make sure I could communicate properly with the web worker. So this was the initial worker.js:

这里有一些管道可以为实际测试做准备,但是最初我只是想确保可以与Web Worker正确通信。 这就是最初的worker.js

And while it worked like a charm — it produced results which I should have been expecting, but wasn’t:

虽然它像一种魅力一样工作,但是却产生了我应该期待的结果,但并非如此:

Being so used to synchronicity in JS, I couldn’t help but be surprised by this. The first moment I saw it my brain registered a bug. But, since each loop sets up a new web worker and they run asynchronously, it makes sense that the numbers won’t be printed out in order.

如此习惯于JS中的同步性,我对此感到惊讶。 看到它的第一刻,我的大脑出现了一个bug。 但是,由于每个循环都设置了一个新的Web Worker,并且它们异步运行,因此有意义的是数字不会按顺序打印出来。

It may have surprised me, but it was working as expected. I could go ahead with the test.

它可能使我感到惊讶,但是它按预期运行。 我可以继续进行测试。

What I wanted is for the web worker’s onmessage function to register t0, call setTimeout, and then immediately exit so as not to block the stack. I could, however, put additional functionality inside the callback, after I’ve set the value of t1. I added my postMessage into the callback, so it doesn’t block the stack:

我想要的是让Web Worker的onmessage函数注册t0 ,调用setTimeout ,然后立即退出以免阻塞堆栈。 但是,在设置t1的值之后,我可以在回调中添加其他功能。 我将postMessage添加到回调中,因此它不会阻塞堆栈:

And here is the main.js code:

这是main.js代码:

This version has a problem.

这个版本有问题。

Of course — since I’m new to web workers I hadn’t considered it at first. But, when multiple runs of the function kept printing 0, I figured something wasn’t right.

当然-因为我是Web工作者的新手,所以我最初没有考虑过。 但是,当函数的多次运行保持打印0 ,我发现有些不正确。

When I printed the sums from within onmessage I got my answer. The main function was moving on synchronously, and wasn’t waiting for the message from the worker to return, so it calculated the average before the web worker was done.

当我从onmessage打印总和时,我得到了答案。 主要功能是同步运行,并且没有等待工作人员返回的消息,因此它在完成网络工作人员之前计算了平均值。

A quick and dirty solution is to add a counter and do the calculation only when the counter has reached the maximum value. So here is the new main.js:

一种快速而肮脏的解决方案是添加一个计数器,并仅在计数器达到最大值时才进行计算。 所以这是新的main.js:

And here are the results:

结果如下:

main(10): 0.1

main(10)0.1

main(100) : 1.41

main(100)1.41

main(1000) : 13.082

main(1000)13.082

Oh. My. Well, that’s not great, is it? What’s going on here?

哦。 我的 好吧,那不是很好,不是吗? 这里发生了什么?

I sacrificed performance testing to get a look inside. I’m now logging t0 and t1 when they are created, just to see what’s going on there.

我牺牲了性能测试来了解内部。 现在,我在创建t0t1时记录它们,只是为了查看发生了什么。

And the results:

结果:

Turns out my expectation of t1 being calculated immediately after t0 was also misguided. Basically the fact that nothing about web workers is synchronous means that my most basic assumptions about how my code behaves just don’t hold true anymore. It’s a difficult blind spot to see.

原来我对t1期望也在t0被误导之后立即被计算出来。 基本上,关于Web Worker的所有信息都不是同步的,这意味着我对代码行为的最基本假设不再成立。 这是一个很难看到的盲点。

Not only that, but even the results I got for main(10) and main(100), which originally made me very happy and smug, were not something I could rely on.

不仅如此,甚至我最初使我感到非常高兴和自鸣得意的main(10)main(100)获得的结果也不是我可以依靠的。

The asynchronicity of web workers also makes them an unreliable proxy for how things behave in our regular stack. So, while measuring performance of setTimeout in a web worker gives some interesting results, these are not results which answer our question.

Web Worker的异步性也使它们成为我们常规堆栈中行为方式的不可靠代理。 因此,尽管在Web Worker中测量setTimeout性能会得出一些有趣的结果,但这些结果并不能回答我们的问题。

教科书方法 (The textbook method)

I was frustrated… could I really not find a vanilla JS solution which would both be elegant and prove my colleague wrong?

我很沮丧……我真的找不到合适的JS解决方案,既优雅又证明我的同事错了吗?

And then I realized— there was something I could do, but I wouldn’t like it.

然后我意识到-我可以做些什么,但我不喜欢。

I could call setTimeout recursively.

我可以递归调用setTimeout

Now when I call main it will call testRunner which measures t0 and then schedules the callback. The callback then runs immediately, calculates t1 and then calls testRunner again, until it’s reached the desired number of calls.

现在,当我调用main ,它将调用testRunner ,它测量t0并安排回调。 然后,回调立即运行,计算t1 ,然后再次调用testRunner ,直到达到所需的调用次数。

The results of this code were particularly surprising. Here are some printouts of main(10) and main(1000):

该代码的结果特别令人惊讶。 以下是main(10)main(1000)一些打印输出:

The results are significantly different when calling the function 1,000 times compared to calling it 10 times. I’ve tried this repeatedly and got largely the same results, with main(10) coming in at 3–4 ms, and main(1000) topping 5 ms.

调用该函数1000次与调用该函数10次相比,结果显着不同。 我已经反复尝试了这一点,并且得到了大致相同的结果,其中main(10)在3-4毫秒内进入,而main(1000) 5毫秒内达到最高。

To be honest, I’m not sure what’s happening here. I searched for an answer, but couldn’t find any reasonable explanation. If you’re reading this and have an educated guess of what’s going on — I’d love to hear from you in the comments.

老实说,我不确定这里发生了什么。 我搜索了一个答案,但是找不到任何合理的解释。 如果您正在阅读本文,并且对正在发生的事情有一个有根据的猜测,那么我很乐意在评论中听到您的意见。

久经考验的真实方法 (The tried and true method)

Somewhere in the back of my mind, I always knew it would come to this… Flashy things are nice for those who can get them, but tried and true will always be there in then end. Even though I tried to avoid it, I always knew this was an option. setInterval.

在我脑海中的某个地方,我一直都知道会发生这种情况……浮华的事物对那些可以得到它们的人来说是不错的,但是经过尝试和真正的努力永远都会在那里。 即使我试图避免这种情况,我始终知道这是一个选择。 setInterval

This code does the trick with somewhat brute force. setInterval runs the function repeatedly, waiting 50 ms between each run, to make sure the stack is clear. This is inelegant, but tests exactly what I needed.

这段代码用某种蛮力来解决问题。 setInterval重复运行该函数,每次运行之间等待50 ms,以确保堆栈是透明的。 这很不雅致,但确实测试了我所需要的。

And the results were also promising. Times seem to match my original expectation — under 1.5ms.

结果也很有希望。 时间似乎符合我的最初期望-在1.5毫秒以下。

Finally I could put this case to bed. I’d had some ups and downs, and my share of unexpected results, but in the end only one thing mattered — I had proven another developer wrong! That was good enough for me.

最后我可以把这个案子放到床上。 我经历了一些起伏,也遇到了一些意外的结果,但是最后只有一件事情很重要-我证明了另一位开发者是错的! 这对我来说已经足够了。

Want to play around with this code? check it out here: https://github.com/NettaB/setTimeout-test

想玩这个代码吗? 在这里查看: https : //github.com/NettaB/setTimeout-test

翻译自: https://www.freecodecamp.org/news/the-curious-case-of-performance-testing-settimeout-0-347059a28acf/

settimeout 性能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值