Chrome高性能网络 -- Chrome内部的各种优化


现在我们对于Chrome网络栈的宏观架构已经烂熟于心,让我们更近一步,了解各种各样面向用户的优化。


优化冷启动体验

当你第一次启动浏览器时,它对你的网站偏好和浏览模式知之甚少。然而,我们大多数人在冷启动后都有相同的习惯,我们可能访问我们的邮箱,喜爱的新闻网站,一个社交网站,内部门户网站等等。不同人的偏好差别很大,但类似的习惯使Chrome预测器能够加速你的冷启动体验。

浏览器启动后,Chrome记住10个最可能被用户访问的域名,注意这并不意味着是全球最受欢迎的十个网站,它只是一个全新启动时使用的列表(这个列表会随着使用不断变化)。随着浏览器的启动,Chrome会对这些域名进行预解析。如果你很好奇,你可以通过浏览chrome://dns查看你的启动域名列表。在网页的顶端,你可以看到你的配置使用的启动候选域名。
                                                  
图5.1是我的Chrome配置使用的列表。我是怎么开始使用我的浏览器的呢?如果我在写文章,我就会浏览Google Docs。我们会在列表中看到很多Google的域名,这应该属于预料之中的事。

通过Omnibox优化交互

Chrome的创新之一是引入了Omnibox,Omnibox处理的事情远不止目标URLs。除了记住用户之前访问过的网页的URLs,它还会对访问历史进行全文搜索,同时集成了你选择的搜索引擎(相当于搜索引擎的输入框)。
用户键入时,Omnibox会自动提示一个动作(an action),可能是基于你的浏览历史的一个URL,也可能是一个查询词条。在后台,针对每一个提示动作,会基于对这一查询的反应以及它过去的性能,来做出评分。实际上, 通过访问chrome://predictors,我们可以看到这些数据。
                          
Chrome维护一份表格,表格的每一行包括用户历史输入前缀,曾提示的条目,以及相应点击率。对于我自己的配置,你可以看到,当我输入g的时候,我有%76的概率会访问Gmail,我再输入一个'm'(组成"gm")后,信心指数上升到99.8%。记录显示,在总计422次的访问中,只有一次在我输入"gm"后最终没有访问Gmail。网络堆栈是怎么做的呢?可能的候选者的颜色对ResourceDispatcher来说是重要的信号。如果我们有一个可能的候选者(黄色),Chrome将发起一个对目标主机的DNS预解析。如果我们有一个高可能性的候选者(绿色),Chrome除了DNS预解析,还会触发TCP预连接。如果这两步都完成了,用户还在考虑,Chrome甚至会在后台预渲染整个网页。
然而,如果在过去的浏览历史中无法找到一个当前输入前缀的匹配,Chrome预期会有一个的搜索请求,可能发起一个针对你的搜索引擎的DNS预解析和预连接。
用户平均会花费几百毫秒来输入并且评估自动完成建议。Chrome可以在后台预解析、预连接,在某些情况下甚至预渲染网页,这样,当用户准备好敲击"enter"时,我们已经完成很多事情,很多网络延迟就被消除了。

使用预解析优化DNS

前面已经多次提到DNS预解析,在我们深挖细节之前,我们先来汇总下DNS预解析的场景和理由:
  • 运行在渲染进程中的Blink文档解析器,会为当前页面中的所有链接提供一个主机名列表,Chrome可以选择是否提前解析。
  • 用户打开页面之前,渲染进程会触发鼠标悬停或按键事件
  • Omnibox可能触发一个针对高可能性建议页面的预解析
  • Predictor会基于过往浏览记录和资源请求数据发起主机解析请求
  • 页面本身可能会显示的要求Chrome对某些主机名进行预解析
上述各种情况对于Chrome都只是线索。Chrome并不保证预解析一定会被执行,所有的线索都会由Predictor进行评估,以决定后续的操作。最坏的情况下,可能无法及时 解析主机名,用户就不得不等待一个DNS解析时间,接着是TCP连接时间,最后是资源加载时间。不过,如果发生这种情况,Predictor会注意到,并据此相应调整将来的决策。
总之,会越用越快。

有一种优化我们还没提到,Chrome能够学习每个站点的拓扑结构,并使用这些信息加速将来的浏览。回想一下,一个网页平均由88个资源组成,来自15个以上不同的主机。每次当你执行浏览时,Chrome都可能记录下该网页上热点资源的主机名,在将来浏览时,它可能选择对其中一些或全部触发一个DNS预解析甚至预连接。
你可以访问 chrome://dns来查看Chrome存储的子资源列表,列表很长,你可以搜索查找目标主机。在上面的例子中,你可以看到Chrome为Google+记住了6个子资源主机,以及每个主机的DNS预解析触发统计,还有预连接执行的次数,以及每个主机的预期请求数量。这些内部统计使Chrome predictor能够执行它的优化。
除了这些内部信号,站长还可以在他们的网页上嵌入额外的标记来请求浏览器预解析某个主机:
  <link rel="dns-prefetch" href="//host_name_to_prefetch.com">
为什么不简单的依赖浏览器中的自动化机械呢?因为有的情况下,你可能想要解析一个网页上没有提到的主机。重定向是一个典型的例子。Chrome本身不能推断出这种模式,但你可以通过提供一个手工的提示来帮助它,使浏览器可以提前解析真正的目标主机名。
这些在内部是如何实现的呢?确切来说,不同版本的Chrome实现是不一样的,团队一直在试验新的更好的方法来改进性能。然而,广泛的来讲,Chrome内部的DNS模块有两个主要的实现。历史上,Chrome依赖于平台独立的getaddrinfo()这个系统调用,把实际的解析职责委托给了操作系统。这种方式正在被Chrome自己的实现,异步DNS解析器(an asynchronous DNS resolver)所代替。
最初依赖操作系统的实现,也有它的优点:代码简洁,可以使用操作系统的DNS缓存。但是,getaddrinfo()是一个阻塞系统调用,这意味着Chrome必须维护一个专门的线程池,以允许它并行执行多个DNS查询。这个线程池上限是6个线程,这是一个经验数量,更多的并行请求数量可能超过某些用户的路由器负荷。
对线程池中的每一个预解析,Chrome简单的调用getaddrinfo(),这会阻塞线程直到解析响应准备好,然后丢掉返回结果开始处理下一个请求,解析结果由OS的DNS cache缓存,在下次查询时,由getaddrinfo()立即返回。实际上,这种方式简单,有效,工作的很好。
好了,有效,但不代表足够好。getaddrinfo()调用对Chrome隐藏了很多信息,例如每一条记录的TTL时间戳,以及DNS缓存本身的状态。为了提高性能,Chrome团队决定实现他们自己的,跨平台的异步DNS预解析器。
                      
新的解析器带来许多新的优化:
  • 更好的控制重传计时器,以及能够并行执行多个查询
  • 可以看到记录的TTLs,使Chrome可以提前刷新热门记录
  • 更好的适应双栈实现(ipv4, ipv6)
  • 基于RTT和其它信号,把故障分布到不同的服务器(failovers to different servers, based on RTT or other signals,这里不理解 -_-!)
上述全部,是Chrome内部持续试验和改进的理念。这给我们带来一个显而易见的问题:我们怎么知道以及怎么测量上述的想法带来的影响呢?很简单,Chrome跟踪每一个用户的详细性能统计。访问chrome://histograms/DNS可以查看收集到的相关数据。
              
上图显示了DNS预取请求的延迟分布:大约50%的预取查询在20ms(第7行)内完成。注意,这些数据是基于最近的浏览会话,并且是用户私有的。如果用户选择参与Chrome的体验改善计划,这些统计数据就会定期反馈给工程团队,这样他们就能看到它们的实现产生的影响并适当调整。

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

我们已经预解析了主机名,而且有一个由Omnibox,或者Chrome predictor估计的高可能性的浏览事件即将发生。为什么不更进一步,在用户触发请求之前,同样预测性的预连接目标主机并完成TCP三次握手呢?这样,我们可以消除另一个RTT延迟,很容易就为用户节约了几百毫秒。这正是TCP预连接所做的。
想要看触发了与哪些主机的预连接,你可以打开一个新的tab并访问chrome://dns
首先,为了避免TCP握手和慢启动带来的副作用,Chrome检查它的socket池,看看是否有一个到该主机的可用连接,可能会有一个可以被重用的keep-alive连接保存在连接池中一段时间。如果没有可用的连接,Chrome就发起一个TCP握手,并把它放入socket池中。然后,当用户触发浏览时,HTTP请求就可以立即发出。
为了满足好奇心,Chrome在chrome://net-internals#sockets提供了一个功能,你可以通过它查看Chrome中所有打开的sockets的状态。截屏如Figure 1.10所示。
                
你还可以深入每一个socket内部,检查它的时间线:连接时间、代理时间、到达时间等。最后,你还可以导出这些数据,以供将来的分析或报告一个bug用。一个好的仪器对任何性能优化都是重要的。

使用预读提示优化资源加载

有时,网页作者能够基于他们网站的布局提供额外的浏览,或者网页上下文,帮助浏览器为用户优化浏览体验。Chrome支持两个这样的提示,它们能够被嵌入网页标记当中:
 <link rel="subresource" href="/javascript/myapp.js">
  <link rel="prefetch"    href="/images/big.jpeg">
prefetch和subresource看起来很相似,不过它们语义完全不同。当一个资源链接指定它的关系为“prefetch”,它是在告诉浏览器该资源会在后面的浏览中很可能会用到。换句话说,这是个有效的交叉网页提示。相反,如果一个资源指定它的关系为“subresource”,这对浏览器来说,就是一个提前的提示,该资源是当前网页用到的,应该用到它之前预先发出一个资源请求。
不同的语义导致资源加载器(resource loader)大相庭径的行为。标记为“prefetch"的资源被认为是低优先级的,在当前网页加载完成后可能会被下载下来。"subresource"资源被以高优先级预取,当前网页中的子资源会相互竞争。
这两个提示,用的好的话,能帮助浏览器大大提升你的网站的浏览体验。最后,要注意prefetch是HTML5标准的一部分,现在Firefox和Chrome都支持它,而subresource只有Chrome支持。

使用Browser Prefreshing 优化资源加载

不幸的是,并不是所有的站长都能够或愿意在他们网页标记中给浏览器提供subresource提示。而且,就算他们提供了,我们也必须等到从服务器取到HTML文档,之后才能解析这些提示并开始获取需要的子资源,等待时间可能是几百甚至上千毫秒,这取决于服务器响应时间和客户端与服务器之前的网络延迟。
不过,我们之前已经看到,Chrome能够通过学习热点资源的主机名来执行DNS预解析。因此,为什么不这样做呢,更进一步,执行DNS查询,使用TCP预连接,然后同样推测性的预取资源。这正是Browser Prefreshing可以做的:
  • 用户初始化一个请求到目标URL
  • Chrome查询predictor,获得与该URL关联的子资源,然后发出一系列DNS预解析,TCP预连接,和资源prefreshing
  • 如果子资源在缓存中,就把它从硬盘加载到内存
  • 如果子资源不在缓存中,或者已经过期,就发出一个网络请求
Browser Prefreshing是一个说明Chrome理论中的实验性优化应用流程的好例子,它应该能够带来更好的性能, 不过仍有很多东西要权衡。只有一种方法可以靠谱的断定它是否已达到标准可以被集成到Chrome中:实现,作为一个实验性功能发布,给真正的用户,在真实的网络中按照正常的使用方式来使用,收集反馈结果
2013年初,Chrome团队处于讨论实现的阶段。如果收集的结果表明它达到预期的结果,我们就可能在今年晚些时候在Chrome中看到prefreshing。改进Chrome网络性能之路永无止境,团队会一直不断试验新的方法,理念和技术。

使用预渲染优化网页浏览

我们之前讨论的优化都是为了帮助降低浏览延迟。然而,需要怎么做才能达到真正的即时体验呢?根据我们之前看到的UX数据,浏览器应该在100ms内有所反应,根本没给网络延迟留什么空间。我们又怎么能在100ms内把网页显示出来呢?
当然,你可能已经知道答案。很多用户都是这么做的:打开多个标签页,在多个标签页之间进行切换,当这个标签还没加载好,我可以切换到别的标签页。毫无疑问,这比你只在一个标签页进行浏览要快得多。如果浏览器提供一个API来做这些事情怎么样?
  <link rel="prerender" href="http://example.org/index.html">
如你所猜,那就是Chrome中的预渲染。"prerender"属性告诉Chrome,它应该在一个后台tab中渲染该网页,包括它的所有子资源,而不仅仅是下载单个资源。后台tab对用户是不可见的,但当用户点击浏览时,该tab会立即切换到前台来,给用户带来“即时体验”。
很好奇想要试试?你可以通过访问 prerender-test.appspot.com  做一个实际的演示,然后通过访问 chrome://net-internals/#prerender可以看到预渲染网页的历史和状态。
          
如你所想,在后台tab中渲染整个网页会消耗很多资源,包括CPU和网络,因此只有在我们万分确定某个网页将要被访问时才使用预渲染。例如:当你使用Omnibox时,高可能性的提示可能触发预渲染。
类似的,Google Search也使用类似的原理,当它估计某个搜索结果是个大概率目标时,它就会添加预渲染提示。
注意,你也可以在你的站点添加预渲染提示。在这样做之前,你应该要记住预渲染有很多限制和局限:
  • 在所有进程中,最多只能有一个预渲染tab
  • HTTPs和需要验证的HTTP网页,不允许预渲染
  • 如果请求的资源,或者它的任何子资源是非幂等请求的,则禁止预渲染
  • 所有资源通过最低网络优先级获取
  • 网页使用最低CPU优先级渲染
  • 内存需求超过100MB的网页
  • 插件初始化延迟,或者有HTML5 media元素
上述情况不允许预渲染。
换句话说,预渲染不一定发生,抑或只应用于确定安全的网页。此外,因为JavaScritp和其它逻辑都有可能在隐藏网页中执行,所以你最好使用Page Visibility API来检测网页是否可见。

越使用越快

无需多言,Chrome的网络栈当然远不只是管理socket。我们旋风般的浏览了Chrome的多层次优化,这些优化是在你浏览网页时在后台自动完成的。Chrome从网页拓扑结构和你的浏览模式中学的越多,它就能做的更好。Chrome会随着你的使用变得更快,简直像魔术一样。但这不是魔术,你已经知道它是怎么做的了。


原文:http://www.aosabook.org/en/posa/high-performance-networking-in-chrome.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值