`
zawa
  • 浏览: 58407 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Javascript之UI线程与性能优化

阅读更多

    在浏览器中,Javascript执行与UI更新是发生在同一个进程(浏览器UI线程)中的。UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲时被提取出来执行。所以Javascript的执行会阻塞UI更新;反之,UI更新也会阻塞Javascript的执行。给用户的表现就是浏览器在工作时短暂或长时间失去反应,用户的操作不能及时得到响应。而UI线程的阻塞很多时候是由于我们要在代码里进行长时间的脚本运算,超过了浏览器限制,导致浏览器失去响应,冻结用户界面。

    所以,编码时对于耗时较长的运算我们不得不考虑UI线程的问题,《High Performance JavaScript》建议javascript操作耗时不应该超过100毫秒,那么这个100毫秒是如何得出来的呢?

    我们来看下2个相关的研究:

1.1、Definitions of Response Time

    引用:Robert B. Miller:《Response time in man-computer conversational

    不同类型的响应以及响应延时,适用于不同的行为水平。响应时间是基于对心理依据的估计而定义的。Robert Miller在其研究中定义了17种不同类型的响应时间,有兴趣的同学可以细读。这里只列举2条比较重要的:

    Topic 1. Response to control activation (开关控制响应)

 

The click of the typewriter key, or the change in control force after moving a switch past a detent position are examples. They indicate responsiveness of the terminal as an object. This response should be immediate and perceived as a part of the mechanical action induced by the operator. Time delay: No more than 0.1 second.

    开关类操作中,终端相应能力对于操作者应该是直接的并可感知的。这类型的延时不应大于0.1秒。

 

 

the delay between depressing the key and the visual feedback should be no more than 0.1 to 0.2 seconds.

    按键与可视反馈延时不应大于0.1~0.2秒。

Note that this delay in feedback may be far too slow for skilled keyboard users.

    研究中同时提到,以上数值对于那些键盘高手还是很慢的。他们的预期值比一般人要高,所以以上数值只是一个普遍适用的数值。



    Topic 13. Graphic response from light pen (光标图形响应)

    
Where the lines are drawn with deliberation by the user—relatively slowly as compared with slashing sketch strokes—a delay of up to 0.1 second seems to be acceptable. There must not be variability perceived by the user in this delay.

    用户的光标输入延时,0.1秒是可被接受的。延时期间用户是动态可感知的。

    
The response delay in the image following the light pen may be as much as one second because the user is not tracing a line but positioning an image that, for him, is completed when his stylus touches the destination for the image.

    在构图中,类似的延时1秒是可以被接受的。



1.2、Response Times: The 3 Important Limits

    引用:Jakob Nielsen:《Response Times: The 3 Important Limits

 

  • 0.1 second is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.
  • 1.0 second is about the limit for the user's flow of thought to stay uninterrupted, even though the user will notice the delay. Normally, no special feedback is necessary during delays of more than 0.1 but less than 1.0 second, but the user does lose the feeling of operating directly on the data.
  • 10 seconds is about the limit for keeping the user's attention focused on the dialogue. For longer delays, users will want to perform other tasks while waiting for the computer to finish, so they should be given feedback indicating when the computer expects to be done. Feedback during the delay is especially important if the response time is likely to be highly variable, since users will then not know what to expect.


    响应时间:3条重要的限制

  • 0.1 senond : 0.1S,限制为达到用户感知系统瞬时响应,意味着无需特别反馈。
  • 1.0 senond : 1.0S,限制为用户思路不被打断,即使用户感觉到延时。通常情况下,在0.1S~1.0S间无需特别的反馈,但用户此次操作确实感觉到了兴趣上的损失。
  • 10 seconds : 10S,限制为用户在对话中的注意力极限;更长的延时会导致用户切换到其它任务以等待计算机完成当前任务。所以,在计算机完成当前任务前需要给予适当的反馈。如果响应时间是高度可变的话,延时期间的反馈是尤其重要的,因为用户不知道接下来将会发生什么。

 

    实际工作中,很多任务都不可能在100ms内完成。很多复杂的运算(例如递归与迭代),都要占用UI线程,用户的其它操作没有得到及时的响应,浏览器界面被冻结、栈溢出等情况时有发生。大部分浏览器在遇到长时间脚本运算时都会给予用户提示是否终止操作,对于很多用户来说,选择终止操作绝对是占大多数的。



2、调用栈大小限制:

    复杂的算法一般都会使用到递归,例如斐波那契序列(Fibonacci sequence), 递归函数的执行会受到浏览器调用栈大小限制(Call Stack Limits)。

    何为调用栈限制?《High Performance JavaScript》里面是这么解释的:

The amount of recursion supported by JavaScript engines varies and is directly related to the size of the JavaScript call stack. With the exception of Internet Explorer, for which the call stack is related to available system memory, all other browsers have static call stack limits. The call stack size for the most recent browser versions is relatively high compared to older browsers (Safari 2, for instance, had a call stack size of 100).

    翻译过来就是:Javascript引擎所支持的递归数量与调用栈大小直接相关。IE除外,它的调用栈大小与系统内存相关,其它浏览器都有固定的调用栈大小限制。大多数现代浏览器的调用栈大小都比老版浏览器要高(例如Safari2,其调用栈大小为100)。



    Figure 4-2. JavaScript call stack size in browsers(图4-2. 浏览器调用栈大小)


    对浏览器的调用栈深浅可以作个简单的测试:

var i = 0;
function fn() {
    fn(i++);
}
try {
    fn();
} catch (e) {
    alert(i);
}

 
    测试结果如下(我只拿了手头上有的浏览器作了个简单的测试,数据仅作参考):

Firefox 6.0 -- 9015
Chrome 14 -- 26176
Opera 11.51 -- 32631
IE7/8  -- 3064
IE6  -- 1131


    从数据来看,递归不是无限制的,不同浏览器的调用栈深浅差别是很大的。递归调用过程,系统会为每一层返回点开辟栈来存储,先是逐级扩展,再是收缩回溯,伴随递归次数的增多,消耗的资源也随之增多,最终可能造成栈溢出。所以在使用递归时,必须要有明确的递归结束条件,也叫递归出口。

    对于重复的运算可以使用Memoization技术来缓存上一次运算结果,以减少重复运算带来的性能损失。Memoization技术主要是利用了散列表(或者叫键值对)来缓存运算结果,查询表比执行函数要快来达到性能优化的目的。

    以下是 菲波拉契数列(Fibonacci sequence) 的一个简单实现:

function fibonacci(n) {
    n = parseInt(n, 10);
    if (n < 2) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

 
    ( 以下数据基于IE8测试)

函数 -- 调用次数(次) -- 耗时(ms)
fibonacci(10) -- 77 -- 0
fibonacci(20) -- 1891 --  16
fibonacci(30) -- 2692537 --   2344


    随着n的递增,调用次数与耗时递增明显。摒弃耗时不说,你会发现浏览器已经无法响应用户操作了,浏览器UI线程已经被阻塞了。fibonacci(40)执行的时候,IE还连续弹出“是否停止运行此脚本”提示。

    使用Memoization技术后,实现的版本如下:

 

var fibonacci = (function() {
    var cache = [1, 1];
    var fib = function(n) {
        if (n > 1) {
            for (var i = cache.length; i <= n; i++) {
                cache[i] = cache[i - 1] + cache[i - 2];
            }
        }
        return cache[n - 1];
    };
    return fib;
})();

 
    迭代取代了递归,没有了浏览器调用栈大小限制,性能上的提升是非常明显的。有兴趣的TX可以自己对比下,效率已经不是一个数量级的了。



3、长时间运行脚本限制

    还是上面使用Memoization技术实现的fibonacci函数,执行fibonacci(10000000),由于运算时间长,触发了浏览器长时间运行脚本限制,UI线程被阻塞了。所以,迭代中有大规模运算,100ms内完成不了的任务可以进行拆分,让javascript短暂让出UI线程控制权,以执行其他任务。

    要短暂让出UI线程控制权,可以使用setTimeout。

    setTimeout的时间精度:
    javascript中定时器是有时间精度的, IE9(非充电模式)、IE8及其以下版本的时间精度是15.6ms;IE9(充电模式)下是4ms;其他浏览器一般也是4ms,低于4ms会降低电池使用寿命。所以,setTimeout(fn, 0)并非马上执行的,其执行时机取决于时间精度。

    为了解决setTimeout在不同浏览器的时间精度问题,W3C因此引入了新的setImmediate()函数。setImmediate与setTimeout类似,setImmediate会在UI线程空闲时将任务插入到队列并执行,我们不再需要关心时间精度的影响。并且,setImmediate执行起来比setTimeout(fn, 0)要快。

    由于任务运行时间的不确定性,在迭代运算中,可以加上运算时间监控,决定此次迭代是否需要拆分任务。

    我们来做一个简单的demo,0~n的累加运算:

// o~n的累加运算
var test = function(n, callback) {
    var result = 0;
    var i = 0;
    (function() {
        var st = +new Date();
        for (; i < n; i++) {
            if ((+new Date()) - st < 100) {
                result++;
            } else {
                setTimeout(arguments.callee, 0); // 运算时间差大于100ms时,中断运算,让出UI线程控制权
                return;
            }
        }
        callback && callback();
    })();
};
test(10000000, showResult); // 1千万次累加

 
    由于额外的流程控制开销,setTimeout方式相对直接运算会消耗更多的时间,好处是UI线程不再阻塞,可以处理更多的任务。

    除了setTimeout方式,将需要长时间运算的操作放到flash里面进行也是一种解决方案,避开javascript单一线程的影响。


    Duff's device(达夫设备):

    Duff's device是一种加速循环的技巧,其思想是尽可能减少循环的执行次数。

/**
* Duff's Device
* http://home.earthlink.net/~kendrasg/info/js_opt/jsOptMain.html#duffsdevice
*/
var n = iterations / 8;
var caseTest = iterations % 8;
do {
    switch (caseTest) {
        case 0:
            testVal++;
        case 7:
            testVal++;
        case 6:
            testVal++;
        case 5:
            testVal++;
        case 4:
            testVal++;
        case 3:
            testVal++;
        case 2:
            testVal++;
        case 1:
            testVal++;
    }
    caseTest = 0;
} while (-- n > 0 );

 
    以上是Duff's device的一个实现版本。

    Duff's device将一个大循环分成每次迭代8次的小循环,8的余数再执行一次小循环,以达到减少循环的次数。

   

    HTML5 web workers:

    随着HTML5技术的发展,在浏览器UI线程外运行javascript代码成为了可能。web workers提供了一个简单的方式让javascript代码在后台线程运行而不影响UI线程。每一个web workers间都是相互独立的,都在自己的线程中运行。

var worker = new Worker('my_task.js');
worker.onmessage = function(event) { // This event handler will be called when the worker calls its own postMessage() function
    console.log("Called back by the worker!\n");
};
worker.postMessage(); // start the worker
worker.terminate(); // terminate a running worker

 
    需要注意的一点是:在web workers内不能操纵DOM,可用于处理与UI线程无关的长时间运行脚本。



一些参考文档:

 

 

2
1
分享到:
评论

相关推荐

    javascript模拟多线程

    该文件可能是关于JavaScript多线程研究的论文,详细探讨了JavaScript模拟多线程的理论、实现和性能优化等方面。阅读这份论文能深入理解JavaScript多线程的底层机制和最佳实践。 总的来说,JavaScript模拟多线程是...

    JavaScript单线程还是多线程

    本文将深入探讨JavaScript的单线程模型以及与之相关的并发机制。 在计算机编程中,线程是程序执行的基本单元,一个进程可以包含多个线程。多线程意味着程序可以在同一时间执行多个不同的任务,而单线程则表示只有一...

    一个用来做性能优化的工具

    在“JavaScript开发-其它杂项”这个标签下,我们可以理解这个工具可能是与JavaScript相关的,可能包含一些优化策略,如任务分割、异步处理或工作线程等。它可能提供了一种机制,将一个long task分解为多个小任务,...

    Concurrent.Thread.js javascript多线程

    `Concurrent.Thread.js` 是一个由日本人编写的JavaScript多线程库,它为JavaScript环境提供了类似传统多线程的能力,帮助开发者更有效地管理任务,避免UI阻塞,提升应用性能。 `Concurrent.Thread.js` 库主要基于...

    javascript多线程

    它们允许在后台线程中执行耗时的计算任务,而不阻塞主线程(UI线程)。这样,即使在进行大量计算时,用户界面也能保持响应。创建Web Worker需要创建一个`.js`文件,然后在主线程中通过`new Worker()`来实例化。...

    一个JavaScript多线程函数

    在浏览器环境中,JavaScript引擎(如V8)是单线程的,所有的代码都在同一个线程上执行,包括UI渲染、事件处理和网络请求等。这种设计使得JavaScript执行高效且易于理解和调试,但同时也限制了其并发能力。 Web ...

    Jquery ajax 同步阻塞引起的UI线程阻塞问题

    在JavaScript和jQuery中,`AJAX`(Asynchronous JavaScript and XML)主要用于实现异步数据通信,使得网页可以在不重新加载整个页面的情况下与服务器交换数据并更新部分网页内容。然而,当你设置`async: false`时,`...

    用户界面多线程

    例如,UI线程可以发送一个消息到后台线程请求数据,后台线程处理完后通过事件通知UI线程更新视图。 5. **线程优先级**:操作系统通常允许设定线程的优先级,以便调整线程执行的顺序。然而,过度依赖优先级可能导致...

    JavaScript多线程编程简介.txt

    4. **OffscreenCanvas**:这是一种用于离屏渲染的技术,主要用于图形处理和动画渲染等场景,可以在后台线程中进行渲染而不影响主UI线程。 #### 五、示例代码解析 根据提供的部分内容,可以看出作者尝试解决的是...

    winform多线程计算调用js

    当需要执行耗时的JavaScript计算时,我们可以将这部分工作放到后台线程上,以免阻塞UI线程。 1. 使用ThreadPool: ```csharp ThreadPool.QueueUserWorkItem(state =&gt; { // 在这里执行JavaScript webBrowser1....

    浅谈Javascript单线程和事件循环.doc

    为了优化性能,我们需要避免单一宏任务执行时间过长,因为它会阻塞其他任务的执行,导致界面无响应。同时,微任务虽然能优先执行,但过多的微任务也会延迟UI的更新,因此应当适度控制微任务的数量。 举例来说,如果...

    详解jQuery同步Ajax带来的UI线程阻塞问题及解决办法

    在讨论jQuery同步Ajax带来的UI线程阻塞问题及其解决办法之前,我们需要了解几个基本概念:JavaScript是单线程的语言,意味着同一时刻只能执行一个任务。页面的渲染和JavaScript的执行都发生在同一个线程上。当...

    High Performance JavaScript PPT

    - **UI线程**(即浏览器事件循环)负责更新用户界面和执行JavaScript代码。 - UI线程每次只能处理一项任务,即界面更新或JavaScript执行。 - 所有的更新任务和JavaScript执行任务都会被添加到UI队列中等待处理。 ##...

    高性能javascript设计

    10. **Web Worker**:对于计算密集型任务,可以利用Web Worker在后台线程中执行,不影响主线程的UI渲染。 11. **性能分析与调试**:使用Chrome DevTools的Timeline、Profiler等工具,找出性能瓶颈,进行有针对性的...

    JavaScript应用实例-ui中的延时除了多线程有别的办法吗.js

    JavaScript应用实例-ui中的延时除了多线程有别的办法吗.js

    Weex架构简介和性能优化.pptx

    总结起来,Weex是一个强大且灵活的跨平台开发框架,其性能优化主要聚焦在网络、渲染和JS线程等关键环节。通过深入理解Weex的架构,并结合最佳实践,开发者可以构建出高性能、响应迅速的移动应用。

    Javascript定时器 一 单线程 修正

    在这个主题中,我们将深入探讨JavaScript中的定时器机制,特别是与单线程环境相关的方面。 首先,JavaScript是一种解释型、基于原型的、弱类型的脚本语言,其在浏览器环境中运行时,遵循一种称为“事件循环”...

    多线程WebBrowser

    这个标题和描述暗示我们讨论的是如何在一个应用程序中利用多线程技术来优化Web浏览器组件的使用,以提高性能和用户体验。下面将详细介绍这个主题,并探讨相关的知识点。 首先,WebBrowser控件是.NET Framework提供...

    使用HTML5 Web Worker提高Web的应用性能研究.pdf

    3. Web Worker的优点:Web Worker可以独立于浏览器UI线程,运行在后台,不会影响浏览器的性能。用户可以进行任何操作,而不受JavaScript代码的影响。 4. Web Worker的适用领域:Web Worker适合异步交互、大规模计算...

Global site tag (gtag.js) - Google Analytics