优化网页浏览器中的页面加载

众所周知,网页浏览器中的页面加载速度受到可用连接带宽的限制。然而,事实证明带宽并非唯一的限制因素,在许多情况下甚至不是最重要的因素。

上图显示了使用最近的 WebKit 完全加载《华尔街日报首页》(因其复杂性很好地代表了许多现代网站而选择)所需的时间。每次页面加载前都清除了浏览器缓存。Mac OS X dummynet 工具用于通过引入数据包延迟和限制可用带宽来模拟各种网络条件。由于测试是针对实时网站进行的,实际连接延迟也是一个因素(到 wsj.com 的 ping 时间约为 75 毫秒)。

从图中可以清楚地看到,虽然可用带宽是一个重要因素,但连接延迟也同样重要。仅增加 50 毫秒的额外延迟,在高带宽情况下页面加载时间就翻了一番(从约 3200 毫秒到约 6300 毫秒)。

延迟是一个显著的现实问题。无线网络技术通常固有高延迟。干扰导致的数据包丢失和重传使情况变得更糟。地理距离引入了延迟。仅美国东海岸和西海岸之间的往返延迟就在 70 毫秒左右。负载重的 Web 服务器可能无法立即响应。

为什么延迟对页面加载速度有如此巨大的影响?毕竟,要完全加载一个页面,网页浏览器只需要获取页面源和所有关联的资源。浏览器会建立多个与服务器的连接,并尝试尽可能多地并行加载资源。如果启动加载单个资源的时间稍微长一点,为什么会有很大影响呢?在此期间其他资源应该正在加载,可用带宽应该仍然得到充分利用。

事实证明,“找出所有关联资源”是这个难题的难点。浏览器在完全解析文档之前并不知道它应该加载哪些资源。当浏览器第一次收到文档的 HTML 文本时,它会将其送入解析器。解析器根据文档构建一个 DOM 树。当浏览器看到引用外部资源的元素(如 <img>)时,它会从网络请求这些资源。

当文档包含对外部脚本的引用时,问题就开始了。任何脚本都可以调用 document.write()。在脚本完全加载并执行,并且任何 document.write() 输出已插入到文档文本中之前,解析无法进行。由于脚本加载期间解析不会进行,因此也不会发出其他资源的进一步请求。这很快导致一种情况:脚本是唯一正在加载的资源,并且连接并行性根本没有得到利用。一系列脚本标签实际上是串行加载的,极大地放大了延迟的影响。

加载额外资源的脚本使情况变得更糟。由于这些资源在脚本执行之前是未知的,因此尽快加载脚本至关重要。最糟糕的情况是加载更多脚本的脚本(通过使用 document.write() 写入 <script> 标签),这在 Javascript 框架和广告脚本中是一种常见模式。

最新的 WebKit nightlies 包含一些新的优化,以减少网络延迟的影响。当脚本加载暂停主解析器时,我们会启动一个辅助解析器,遍历 HTML 源的其余部分以查找更多要加载的资源。我们还会优先加载脚本和样式表,使其在图像之前加载。总体效果是,我们现在能够与脚本并行加载更多资源,包括其他脚本。

从上图中可以看出,这些优化显著降低了网络延迟的影响,并普遍提高了页面加载速度。例如,在模拟 50 毫秒延迟且无带宽限制的情况下,总页面加载时间快了 2.8 秒(从 6.3 秒到 3.5 秒)。当带宽限制为 512kbit/s 时,改进了 5.9 秒(从 23.8 秒到 17.9 秒)。