浏览器应该使用所有的可用内存吗?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"},{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong"}],"text":"本文最初发布于 "},{"type":"text","marks":[{"type":"italic"},{"type":"strong"}],"text":"Julio Merino 的个人"},{"type":"text","marks":[{"type":"italic"},{"type":"color","attrs":{"color":"#333333","name":"user"}},{"type":"strong"}],"text":"博客,经原作者授权由 InfoQ 中文站翻译并分享。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"这篇文章在我的草稿箱里呆了 3 年了。我之所以一直犹豫着没发,一部分原因是本文探讨的内容是一个比较难以达成共识的想法。另一部分原因是,如果只看这个激进的标题,可能会引发不小的争议。无论如何,现在是时候发布出来了!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据"},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/Betteridge%27s_law_of_headlines","title":null,"type":null},"content":[{"type":"text","text":"Betteridge的标题法则"}]},{"type":"text","text":",标题中的问题,答案一般是“不“。我同意,但有一些点需要注意。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们都见过类似这样的讨论,尤其是在 Hacker News 上:首先是有人抱怨,像谷歌 Chrome 这样的应用程序很浪费资源,因为它会消耗好几 GB 的内存。然后就有人过来说,这些内存是用来提升速度的,因此,这是正确的行为:如果计算机有数 GB 的空闲内存,像 Chrome 这样的应用程序应该将所有可用内存用作缓存,从而尽可能地提高响应速度。好像很有道理,是吗?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是的,有道理,"},{"type":"text","marks":[{"type":"italic"}],"text":"但前提得是只有 Chrome 一个应用程序在运行"},{"type":"text","text":"。然而,这种情况并不常有,不是吗?总是会有多个程序同时运行(考虑下系统服务,还有像 Teams 这样的重量级程序),也就是说,那也许不是最好的主意。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文将探讨两个问题,一是为什么最好不要允许应用程序占用所有的可用内存作为缓存,二是关于这一问题的可能的解决方案。我将以 Chrome 为例,因为它经常成为抱怨的对象,但本文探讨的内容也同样适用于所有其他的浏览器,以及大多数现代化的大型程序。但是,在此之前,让我们先回顾一些内存管理的基础知识,以确保我讲的和你想的是同一件事。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"内存分页回顾"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于运行在其上的每个应用程序(进程),现代计算机提供的内存地址空间基本上可以说是无限的。每个进程都认为只有自己在运行,有海量的内存可供自己使用,从地址 0 到 2^64(在 64 位的机器上)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"显然,事实并非如此:还没有计算机有 2^64 物理字节的内存。计算机处理器将物理内存划分成固定大小的"},{"type":"text","marks":[{"type":"italic"}],"text":"页帧(page frames)"},{"type":"text","text":"(通常是 4KB),将每个进程的虚拟内存划分成"},{"type":"text","marks":[{"type":"italic"}],"text":"页(pages)"},{"type":"text","text":"。然后,操作系统内核负责将虚拟内存页子集映射到物理页帧,对于每一次内存访问,处理器会处理虚拟地址和物理地址之间的转换。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这种设计下,虚拟地址空间比物理内存要大许多,多个进程(这样会有多个虚拟地址空间)可以同时运行。换句话说:物理内存被故意"},{"type":"text","marks":[{"type":"italic"}],"text":"过度使用(over-committed)"},{"type":"text","text":"。当一个进程在其虚拟地址空间中分配了较多的内存,可能会没有足够的物理内存页来支撑新分配的虚拟页。这样就会导致"},{"type":"text","marks":[{"type":"italic"}],"text":"内存紧张(memory pressure)"},{"type":"text","text":",内核将做些事情来缓解这种情况,尝试满足新的内存请求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在内存紧张的情况下,内核会做什么,这取决于操作系统,但通常来说,内核必须找到已经映射的页(可以是任何进程的)并把它们驱逐,从而释放页帧,为新的内存请求腾出空间。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以将内存页分成以下两类。要了解更多背景信息,我建议你读下"},{"type":"link","attrs":{"href":"https:\/\/www.usenix.org\/conference\/2000-usenix-annual-technical-conference\/ubc-efficient-unified-io-and-memory-caching","title":null,"type":null},"content":[{"type":"text","text":"NetBSD关于统一缓冲区缓存的论文"}]},{"type":"text","text":":"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"文件页"},{"type":"text","text":"对应的内存块直接来自磁盘文件。如果没有修改的话,这些页可以随意丢弃,如果修改了,则可以刷写到它们的后备文件。例如:用于运行可执行代码的页总是可以丢弃,因为它们是只读的,而且磁盘上有后备文件,而通过 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#e83e8c","name":"user"}}],"text":"mmap(2)"}]},{"type":"text","text":" 使用的页可能需要,也可能不需要刷写到磁盘,这要视它们的脏状态而定。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"匿名页"},{"type":"text","text":"对应分配给应用程序的内存块(考虑下 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#e83e8c","name":"user"}}],"text":"malloc"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#e83e8c","name":"user"}}],"text":"new"}]},{"type":"text","text":" )。在内核看来,这些内存的内容是无意义的,因为它们是由程序逻辑 \"动态 \"填充的,没有后备资源。这样一来,如果要驱逐这些页,就没有一个文件可供刷写,所以内核就得把它们放在其他某个地方。那个某个地方就是"},{"type":"text","marks":[{"type":"italic"}],"text":"交换区(swap area)"},{"type":"text","text":"。"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重要提示:关于页驱逐的一个关键细节,也是你阅读本文接下来的内容时必须留意的一个关键细节,就是"},{"type":"text","marks":[{"type":"italic"}],"text":"页的原始来源不同"},{"type":"text","text":",页驱逐进程会有所不同。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果系统无法找到足够的页来驱逐(例如,已经驱逐了所有文件页,也已经没有交换空间来驱逐剩余的匿名页),内核就会发生严重错误,或者开始终止进程,尝试释放已使用的内存。在 Linux 上,这是通过备受喜爱的 Out Of Memory killer 机制完成的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至于如何决定从内存中驱逐哪些页,每个内核都有自己的算法。一般来说,内核会实现一个 LRU 算法驱逐最近最少使用的页。但是,它也会考虑每次驱逐的“成本”:"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"成本最低的做法是首先驱逐只读文件页,因为它们不需要通过写磁盘实现持久化,而且可以快速恢复。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"成本第二低的是驱逐脏文件页,因为它们位于文件系统中,可以覆写。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"成本最高的是驱逐匿名页,因为这不可避免地要写交换区——这反过来又可能会涉及某种空间分配。你永远不会希望系统到达必须将页移到交换区的程度,这么做时,性能就很糟糕了。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不过,这里就不介绍具体细节了,那和本文接下来的内容没什么关系。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"同时运行Chrome和Bazel"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在介绍完背景信息后,让我们回到最初的讨论。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了证明 Chrome 大量使用内存的合理性,通常人们给出的论据是,浏览器使用所有内存作为缓存。持有这种观点的人认为,这是好事,因为人们想要快速的浏览体验,尽量多地缓存数据有助于实现那种体验。没错,不过他们忽略了一个小事实,就是 Chrome 不是在真空中运行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我将采用 Raymond Chen 的分析方法“"},{"type":"link","attrs":{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20050607-00\/?p=35413","title":null,"type":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"如果两个程序这样做会怎么样?"}]},{"type":"text","text":"”,说明这为什么是个坏主意。饥饿的浏览器可能与其他程序同时运行,而其中某些程序也可能是内存密集型的。为了使示例场景更真实,我们把 Bazel 加入进来,这个应用程序也喜欢占用大量的内存,以缓存工作空间中数 GB 的构建图。在这个场景中,我们有一名程序员使用 Chrome 在线研究一些信息,编写一些代码(使用某种重量级 IDE),最后使用 Bazel 构建生成的项目。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这种情况下,程序员可能首先会密集地使用 Chrome 做些研究。在这个过程中,Chrome 的内存使用可能会逐渐增加,占用所有可用的内存来缓存页面和图片。然后,程序员可能会运行 Bazel 构建项目。而Bazel可能需要额外消耗大量的内存来加载完全依赖图。但是,此时,Bazel 可能没有找到足够的可用内存,所以操作系统将需要换出 Chrome 的缓存内存。这可能会导致后续切回 Chrome 的时候反应速度慢很多,因为浏览器缓存的东西都被换出了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里的问题是,像 Chrome 和 Bazel 这样的应用程序使用匿名内存来运作它们自己的缓存。按定义,缓存内存可以在任意时间随意丢弃,当出现内存压力时,内核唯一能看到的是这些应用程序分配了大量的匿名内存。内核并不知道这些页中是包含必须持久化的宝贵数据,还是可随意丢弃的易失性数据。由此导致的恶果是,内核可能会决定将缓存数据移到交换区,我前面提到过,一旦用到了交换区,从性能的角度来说,你已经输了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于这一点,我们有什么可以做的吗?"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"协作型内存分配"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你会说,“好吧,我们显然不能允许应用程序把所有内存都拿来自己用。我们应该限制他们最多使用X%的内存,要留一些内存给其他应用程序使用”。(事实上,这就 Bazel 采用的方案。)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这无法解决任何问题:如果应用程序本身负责查看当前可用的内存,然后独自决定应该使用多少内存,那么要么我们最终还会面临上面的情况,要么就是应用程序没有足够的内存可以使用。设想一下,如果我们允许 Chrome 使用所有内存的 80%,因为它知道要留下 20%。然后,Bazel 要运行了,按照配置,它也可以使用可用内存的 80%……是 20% 的 80%,已经很小了。Bazel 受到了巨大的惩罚,只因为它是第二个运行的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以调整这个模型,提出一种有效的方案,但是,通过分布式决策决定如何使用内存是不够的。首先,你需要一个能在整个机器上做出明智决策的神使(内核);其次,你不能相信所有的应用程序都会遵守规则。毕竟,那就是几十年前我们从协作型多任务转到抢占式多任务的原因。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"文件缓存"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们考虑下这样一个场景:有很多应用程序,它们几乎不使用匿名内存,但会大量使用文件系统。这些应用程序每个都会打开许多非常大的文件,对它们执行随机读写操作,而且还打开很长时间。我们同时运行着这些应用程序的多个实例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这种情况下,我们可能会看到,系统总体的内存使用率接近 100%,和之前一样,但交换区仍然是空的。更重要的是,虽然系统可能因为 CPU 和 I\/O 使用率高而变慢,但其性能是可预测的:从命令行执行一条简单的命令 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#e83e8c","name":"user"}}],"text":"ls"}]},{"type":"text","text":" 瞬间就可以完成,不会受内存抖动拖累。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里的情况是,内核现在将所有内存作为其文件缓冲区缓存的一部分;应用程序本身不控制内存。这种内核级的缓存可以跟踪文件页(不是匿名页),通过优化随机 I\/O 和顺序访问(通过预取)来提升 I\/O 性能。通常,该缓存可以占用所有可用的内存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"与前面介绍的 Chrome 和 Bazel 所采用的方案相比,这种基于文件的方案有一个很大的不同,就是由内核控制一个统一的跨应用程序的缓存,内核对缓存中的内容了如指掌。内核可以针对缓存中的内容从整体视角做出决策,尽量保证所有应用程序的正常运行:如果只有一个应用程序在运行,那么所有的文件缓冲区缓存将都供它使用;但是,如果有两个或两个以上的应用程序在运行,那么它们将“公平地”共享缓存——我这里之所以加引号,是因为确实存在相互干扰的问题。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"根本原因"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么,允许 Chrome 使用所有内存作为缓存,真正的问题在哪里?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"简单来说,就是操作系统无法查看应用程序的匿名内存,不能自己做决策。由此导致的结果是,当出现内存压力时,内核唯一能做的事情是,将匿名内存页推出到磁盘——即使那些页包含了易失性缓存——后续再从交换区还原它们成本很高。"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"有什么解决方案吗?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"设想一下,如果内核和应用程序之间有一种可以回收内存的反馈机制。内核可以说,“嗨,Chrome,我内存不够用了,把你不是特别需要的内存释放出来一些吧”,以此请求回收内存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遗憾的是,这是不可行的,因为这得要求所有应用程序配合,在所有情况下都不能做错。流氓应用程序或是存在缺陷的应用程序会囤积内存,导致更糟糕的情况。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"尽管如此,Android 就实现了这样一种方案。Android 的设计就是,系统可以彻底驱逐应用程序的某些部分(活动)。其原理是,系统和应用程序之间有一种协议,通过它可以实现受控的驱逐动作:系统首先会友好地请求应用程序释放内存,并允许应用程序刷写数据,但是,即使应用程序没有按照要求释放内存,系统也会销毁那部分内存。这两种情况都有可能出现,因此,在设计应用程序时必须保证,不管是被优雅地关闭还是强制关闭,它都可以重建状态——就像它从未退出过。Android 之所以有这样一种设计,一个原因是它首先是面向移动设备的,这类设备的内存很小,另一个原因是移动设备同一时间主要运行一个应用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一种解决方案是,有一个系统级的缓存服务,可以处理运行应用程序产生的任意内存对象。这样一个服务可以跨应用程序就内存使用做出协商一致的决策,均匀地删除缓存条目。但是,和前面的解决方案一样,需要所有应用程序的配合。否则,一个不合格的应用程序可能会囤积内存,使得其他所有按规则行事的应用程序都受到惩罚。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这就说到了另一种解决方案:划分内存,预先指定每个程序可以使用的内存大小。容器就是这么做的,但对于个人计算机来说这并不是一个好方案:硬性划分无法动态适应用户的行为。有时候,你就是只想浏览网页,在那种情况下,你会希望浏览器使用所有可用的资源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,我们可以尝试将应用程序都塞进现有系统的设施中。如果应用程序使用文件而不是匿名内存来实现缓存,那么系统的文件缓冲区缓存就可以正常运行。设想一下,每个应用程序都使用单个的文件来存储可缓存的对象,而不是使用 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#e83e8c","name":"user"}}],"text":"malloc"}]},{"type":"text","text":" 来获取匿名内存。这时,应用程序会使用打开\/读取\/关闭循环来访问那些缓存的对象。在这种情况下,内核中的文件缓存就可以跨应用程序做该做的事:经常使用的缓存条目(文件)会驻留内存,如果内存紧张,就可以把它们驱逐到后备文件中,而且成本很低。性能可能会因为额外的系统调用而受影响,但总的结果还是要好些。事实上,"},{"type":"link","attrs":{"href":"https:\/\/varnish-cache.org\/docs\/trunk\/phk\/notes.html","title":null,"type":null},"content":[{"type":"text","text":"Varnish就是这样做的"}]},{"type":"text","text":"!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遗憾的是,上面所有这些解决方案都需要某种跨程序协同,并且需要所有程序都按规则行事。这在设计新系统时也许可行(就像 Android 所做的那样),但将这些东西加装到目前的系统中,肯定是不行的,虽然我希望它可以。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,你可能会认为,在现如今的世界里,上面这样的情况没什么问题,因为计算机有足够的内存。但它们确实还是问题。我之前在谷歌的时候,就想着要让人们能够在 16GB 的笔记本上愉快地使用 Chrome 和 Bazel。每次我的 Surface Go 2 变慢(我一年前新买的机器,但只有 8GB 内存),我就会想起这些问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么,让我们回到最初的问题:“浏览器应该使用所有可用的内存吗?”不应该,不能像现在这样做。但是,如果有更好的机制可以实现有效的跨应用程序缓存,答案就是 Yes 了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文链接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/jmmv.dev\/2021\/08\/using-all-memory-as-a-cache.html","title":null,"type":null},"content":[{"type":"text","text":"Should the browser use all available memory?"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章