• Chrome高性能的秘密:预连接、预加载与预渲染

    一些有效的提速方法
    服务器君一共花费 27.477 ms 进行了 3 次数据库查询,努力地为您提供了这个页面。
    广告很萌的

    优化 DNS 预连接

    前面已经多次提到了 DNS 预解析,在深入实现之前,先汇总一下 DNS 预解析的场景和理由:

    运行在渲染进程中的 WebKit 文档解析器(document parser), 会为当前页面上所有的链接提供一个主机名(hostname)列表,Chrome 可以选择是否提前解析。 当用户要打开页面时,渲染进程先会触发一个鼠标悬停(hover)或按键(button down)事件。

    Omnibox 可能会针对一个高可能性的建议页面发起解析请求。

    Chrome Predictor 会基于过往浏览记录和资源请求数据发起主机解析请求。(下面会详细解释。)

    页面本身会显式地要求 Chrome 对某些主机名称进行预解析。

    上述各项对于 Chrome 都只是一个线索。 Chrome 并不保证预解析一定会被执行,所有的线索会由 Predictor 进行评估,以决定后续的操作。最坏的情况下,可能无法及时解析主机名,用户就必须等待一个 DNS 解析时间,然后是 TCP 连接时间,最后是资源加载时间。Predictor 会记下这个场景,在未来决策时相应地加以参考。总之,一定是越用越快。

    之前提过到 Chrome 可以记住每个页面的拓扑(topology),并可以基于这个信息进行加速。还记得吧,平均每个页面带有 88 个资源,分别来自于 30 多个独立的主机。每打开这个页面,Chrome 会记下资源中比较常用的主机名,在后续的浏览过程中,Chrome 就会发起针对某些主机或者全部主机的 DNS 解析,甚至是 TCP 预连接。

    除了内部事件通知外,页面设计者可以在页面中嵌入如下的语句请求浏览器进行预解析:

    之所以有这个需求,一个典型的例子是重定向(redirects)。Chrome 本身没办法判断出这种模式,通过这种方式则可以让浏览器提前进行解析。

    具体的实现也是因版本而有所差异,总体而言,Chrome 中的 DNS 处理有两个主要的实现:

    1. 基于历史数据(historically), 通过调用平台无关的 getaddrinfo ()系统函数实现。
    2. 代理操作系统的 DNS 处理方法,这种方法正在被 Chrome 自行实现的一套异步 DNS 解析机制(asynchronous DNS resolver)所取代。

    依赖于系统的实现,代码少而且简单,但是 getaddrInfo()是一个阻塞式的系统调用,无法有效地并行多个查询操作。经验数据还显示,并行请求过多甚至会超出路由器的负额。 Chrome 为此设计了一个复杂的机制。对于其中带有 worker-pool 的预解析, Chrome 只是简单的发送 getaddrinfo() 调用, 同时阻塞 worker thread 直到收到响应数据。因为系统有 DNS 缓存的原因,针对解析过的主机,解析操作会立即返回。 这个过程简单,有效。

    但还不够, getaddrinfo()隐藏了太多有用的信息,比如 Time-to-live (TTL)时间戳, DNS 缓存的状态等。于是 Chrome 决定自己实现一套跨平台的异步 DNS 解析器。

    这个新技术可以支持以下优化:

    • 更好地控制重转的时机,有能力并行执行多个查询操作。清晰地记录 TTLs。
    • 更好地处理 IPv4 和 IPv6 的兼容。
    • 基于 RTT 和其它事件,针对不同服务器进行错误处理(failover)

    Chrome 仍然进行着持续的优化。通过 chrome://histograms/DNS 可以观察到 DNS 度量数据:

    上面的柱状图展示了 DNS 预解析延迟时间的分布:比如将近 50%(最右侧)的查询在 20ms 内完成。这些数据基于最近的浏览操作(采样 9869 次),用户可以选择是否报告这些使用数据,然后这些数据会以匿名的形式交由工程团队加以分析,这样就可以了解到功能的性能,以及未来如何进一步调整。周而复始,不断优化。

    使用预连接优化了 TCP 连接管理

    已经预解析到了主机名,也有了由 OmniBox 和 Chrome Predictor 提供信号,预示着用户未来的操作。为什么再进一步连接到目标主机,在用户真正发起请求前完成 TCP 握手呢?这样就可省掉了另一个往返的延迟,轻易地就能为用户节省到上百毫秒。其实,这就是 TCP 预连接的工作。 通过访问 chrome://dns 就可以看到 TCP 预连接的使用情况。

    首先, Chrome 检查它的 socket pool 里有没有目标主机可以复用的 socket, 这些 sockets 会在 socket pool 里保留一段时间,以节省 TCP 握手时间及启动延时(slow-start penalty)。如果没有可用的 socket, 就需要发起 TCP 握手,然后放到 socket pool 中。这样当用户发起请求时,就可以用这个 socket 立即发起 HTTP 请求。

    打开 chrome://net-internals#sockets 就可以看到当前的 sockets 的状态:

    你可以看到每一个 socket 的时间轴:连接和代理的时间,每个封包到达的时间,以及其它一些信息。你也可以把这些数据导出,以方便进一步分析或者报告问题。有好的测试数据是优化的基础, chrome://net-internals 就是 Chrome 网络的信息中心。

    使用预加载优化资源加载

    Chrome 支持在页面的 HTML 标签中加入的两个线索来优化资源加载:

    在 rel 中指定的 subresource (子资源)和 prefetch (预加载)非常相似。不同的是,如果一个 link 指定 rel (relation)为 prefetch 后,就是告诉浏览器这个资源是稍后的页面中要用到的。而指定为 subresource 则表示在本页中就会用到,期望能在使用前加载。两者不同的语义让 resource loader 有不同的行为。prefetch 的优先级较低,一般只会在页面加载完成后才会开始。而 subresource 则会在解析出来时就被尝试优先执行。

    还要注意,prefetch 是 HTML5 的一部分,Firefox 和 Chrome 都可以支持。但 subresource 还只能用在 Chrome 中。

    应用 Browser Prefreshing 优化资源加载

    不过,并不是所有的 Web 开发者会愿意加入上面所述的 subresource relation, 就算加了,也要等收到主文档并解析出这些内容才行,这段时间开销依赖于服务器的响应时间和客户端与服务器间的延迟时间,甚至要耗去上千毫秒。

    和前面的预解析,预连接一样,这里还有一个 prefreshing::

    用户初始化一个目标页面的请求。 Chrome 查询 Predictor 之前针对目标页面的子资源加载,初始化一组 DNS 预解析,TCP 预连接及资源 prefreshing。

    如是在缓存中发现之前记录的子资源,由从磁盘中加载到内存中。

    如果没有或者已经过期了,就是发送网络请求。

    直到在 2013 年初, prefreshing 还是处于早期的讨论阶段。如果通过数据结果分析,这个功能最终上线了,我们就可以稍晚些时候使用到它了。

    使用预渲染优化页面浏览

    前面讨论的每个优化都用来帮助减少用户发起请求到看到页面内容的延迟时间。多快才能带来即时呈现的体验呢?基于用户体验数据,这个时间是 100 毫秒,根本没给网络延迟留什么空间。而在 100 毫秒内,又怎样渲染页面呢?

    大家可能都有这样的体验:同时开多个页签时会明显快于在一个页签中等待。浏览器为此提供了一个实现方式:

    这就是 Chrome 的预渲染(prerendering in Chrome)! 相对于只下载一个资源的“prefetch”, “prerender”会让 Chrome 在一个不可见的页签中渲染一个页面,包含了它所有的子资源。当用户要浏览它时,这个页签被切到前台,做到了即时的体验。

    可以浏览 prerender-test.appspot.com 来体验一下效果,再通过 chrome://net-internals/#prerender 查看下历史记录和预连接页面的状态。

    因为预渲染会同时消耗 CPU 和网络资源,因些一定要在确信预渲染页面会被使用到情况下才用。Google Search 就在它的搜索结果里加入 prerender, 因为第一个搜索结果很可能就是下一个页面(也叫作 Google Instant Pages)。

    你可以使用预渲染特性,但以下限制项一定要牢记:

    • 所有的进程中最多只能有一个预渲染页。 HTTPS 和带有 HTTP 认证的页面不可以预渲染。
    • 如果请求资源需要发起非幂等(non-idempotent,idempotent request 的意义为发起多次,不会带来服务的负面响应的请求)的请求(只有 GET 请求)时,预渲染也不可用。
    • 所有的资源的优先级都很低。
    • 页面渲染进程的使用最低的 CPU 优先级。
    • 如果需要超过 100MB 的内存,将无法使用预渲染。
    • 不支持 HTML5 多媒体元素。

    预渲染只能应用于确信安全的页面。另外 JavaScript 也最好在运行时使用 Page Visibility API 来判断一下当前页是否可见(参考 you should be doing anyway)。

    最后,总之,Chrome 正逐步优化网络延迟和用户体验,让它随着用户的使用越来越快。

更多 推荐条目

Welcome to NowaMagic Academy!

现代魔法 推荐于 2013-02-27 10:23   

本章最新发布
随机专题
  1. [计算机算法] 从双端队列引出的卡特兰数 3 个条目
  2. [数据结构] 散列表(哈希表) 13 个条目
  3. [软件工程与项目管理] 呈现树的构建 13 个条目
  4. [数据库技术] 数据库范式篇 5 个条目
  5. [智力开发与知识管理] 整体性学习策略 9 个条目
  6. [移动开发] Android Studio里的Gradle 3 个条目
  7. [移动开发] Android View注入框架Butter Knife 3 个条目
  8. [PHP程序设计] 命令式编程范式 6 个条目
  9. [PHP程序设计] fsockopen,curl与file_get_contents 12 个条目
  10. [移动开发] Android SQLite增删查改实例(数据:魔弹之王) 2 个条目
  11. [Python程序设计] urls.py设置技巧 8 个条目
  12. [PHP程序设计] 对输入文件类型的检测 1 个条目
窗口 -- [博客]