# 通过缓存原始视图模块查找来优化FCP/LCP

**URL:** https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759
**Category:** Development
**Created:** [2022年一月3日 21:41 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759 "2022-01-03T21:41:03Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![rrit](https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/32.png) [@rrit](https://meta.discourse.org/u/rrit)
#### Post date: [2022年一月3日 21:41 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/1 "2022-01-03T21:41:03Z")

</div>

我正在尝试通过这两个 PR 来改进 First Contentful Paint (FCP) 和 Largest Contentful Paint (LCP)：

> <https://github.com/discourse/discourse/pull/15440>
>
> The function lookupView is called multiple times for each template name while bo…oting up. Calling resolver.customResolve is expensive. Re-using already looked up views improves First Contentful Paint (FCP) and Largest Contentful Paint (LCP) .
> 
> Does not include test coverage as change might be a no-brainer.

> <https://github.com/discourse/discourse/pull/15441>
>
> This improves First Contentful Paint (FCP) and Largest Contentful Paint (LCP) 
> …
> WIP: Any help for testing, refactoring and test coverage is welcomed.

我对这些更改的实际影响非常感兴趣——所以请尝试一下并提供一些反馈。

当然，任何关于测试、重构和测试覆盖率的帮助都非常受欢迎。

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [2022年一月4日 00:59 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/2 "2022-01-04T00:59:58Z")

</div>

第一个看起来是一个简单的优化，我认为这是有意义的，@david 和 @eviltrout 花时间优化了这部分，所以我非常想知道他们对此的看法。

第二个感觉长期来看有点脆弱，我完全理解优化的愿望，但这让我有点担心，因为它将是我们以后需要维护的一个区域。

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2022年一月4日 11:46 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/4 "2022-01-04T11:46:51Z")

</div>

你好 @rrit - 感谢你的 PR。第一个听起来是个不错的改进。你是否能够衡量性能影响？它节省了多少时间？

正如 @sam 所说，第二个的可维护性有点令人担忧。它看起来是从 Ember 源代码复制/粘贴的吗？你是否为了提高性能而更改了某些内容？

---

<div class="post-metadata">

### Author: ![rrit](https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/32.png) [@rrit](https://meta.discourse.org/u/rrit)
#### Post date: [2022年一月4日 14:51 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/5 "2022-01-04T14:51:41Z")

</div>

## lookupView-patch

现在通过 `Map` 实现，而不是 `Array`。

`lookupView` 在应用程序启动时花费的时间 - 开发实例：

- 无 patch： **124 毫秒**  
[Firefox Profiler](https://share.firefox.dev/3qKfEBX) (callstack 过滤到 `lookupView`)

- 有 patch： **9 毫秒**  
[Firefox Profiler](https://share.firefox.dev/3F5YqEx) (callstack 过滤到 `lookupView`)

节省的时间： **约 115 毫秒**

这会将 `appendOutletView` 内部花费的时间从 1.083 毫秒减少到 946 毫秒 - 开发实例。

* * *

## patch on raw handlebar helpers

是的，实际上是复制粘贴，只有一个更改：使用廉价的 `isPath` 检查。

```php
      // replaces @ember/-internals/utils isPath
      // @see: https://github.com/emberjs/ember.js/blob/3537670c14883346e11e841fcb71333384fcbc87/packages/%40ember/-internals/metal/lib/path_cache.ts#L5-L7
      // @see: https://github.com/emberjs/ember.js/blob/255a0dd3c7de1187f4a2f61a97cf78bfff8f66a8/packages/%40ember/-internals/glimmer/lib/utils/bindings.ts#L70
      let isPath = context.indexOf('.') > -1;

```

例如，`renderTopicListItem` 最终会触发许多 `_getPath` 调用（还可以节省 50-100 毫秒）：  
[Firefox Profiler](https://share.firefox.dev/3HwIkFq) (`renderTopicListItem` 内 `_getPath` 的 callstack 过滤)

也许昂贵的 `_getPath` 调用应该在 _Ember.js_ 中优化，而不是在 _Discourse_ 中。

* * *

并查看 Firefox Profiler 以深入了解 JavaScript 执行：

> **[Firefox Profiler - Documentation](https://profiler.firefox.com/docs/)**
>
> Documentation related to the Firefox Profiler, a tool to record and visualize profile.

---

<div class="post-metadata">

### Author: ![eviltrout](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/eviltrout/32/5275_2.png) [@eviltrout](https://meta.discourse.org/u/eviltrout)
#### Post date: [2022年一月4日 18:02 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/6 "2022-01-04T18:02:11Z")

</div>

谢谢您的补丁。第二个补丁似乎有点脆弱。

您的基准测试是在开发模式还是生产模式下运行的？Ember 在这两种模式下的性能差异很大。

---

<div class="post-metadata">

### Author: ![rrit](https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/32.png) [@rrit](https://meta.discourse.org/u/rrit)
#### Post date: [2022年一月4日 23:37 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/7 "2022-01-04T23:37:15Z")

</div>

@david 找到了一种解决此问题的好方法 - 请参阅他在 [github 上的评论](https://github.com/discourse/discourse/pull/15440#issuecomment-1005082500)。

> <https://github.com/discourse/discourse/pull/15456>
>
> da6edc1 introduced the \`lookupView\` method, which initialized a fresh resolver, …and used it to directly look up raw-views (with no caching). This worked well, but was not a clean solution. It required initializing an entirely new resolver, and did not have any caching.
> 
> This commit updates the \`helperContext\` to include access to the registry, and uses it to perform raw-view lookups. As well as re-using the registry, this also means we're making use of the resolver's built-in cache.
> 
> I haven't been able to measure any noticeable performance impact from this change, but there is certainly less work being done, so it may be beneficial on older devices.
> 
> Co-authored-by: Ayke Halder \<rr-it@users.noreply.github.com\>
> 
> For context, see https://meta.discourse.org/t/javascript-performance-optimization-on-fcp-and-lcp/213759/8?u=david

在 Ember 的“production”构建中，“latest”网页上调用 `renderTopicListItem` 的时间从 348 毫秒减少到 201 毫秒。

之前的基准测试仍在开发模式下运行。

* * *

## 如何在 Ember.js 生产模式下运行基准测试？

- [https://review.discourse.org/t/dev-allow-ember-server-environment-production-to-be-used-15431/29090](https://review.discourse.org/t/dev-allow-ember-server-environment-production-to-be-used-15431/29090)

```plaintext
# 在生产模式下启动 ember
d/ember-cli server --environment="production"

```

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2022年一月5日 01:01 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/8 "2022-01-05T01:01:23Z")

</div>

> [@rrit](#):
>
> 在 Ember 的“production”构建中，网页“latest”上调用 `renderTopicListItem` 的时间从 348 毫秒减少到 201 毫秒。

不幸的是，我无法复制这种显著的速度提升。在 Firefox 和 Chrome (macOS) 上，我没有看到任何可衡量的改进。Chrome 在 `renderTopicListItem` 上花费约 23 毫秒。Firefox 约 30 毫秒。在较旧的 Android 设备 (Pixel 3) 上，我看到约 108 毫秒。更改前后数字似乎没有变化。

顺便说一下，我是通过使用 [performance API](https://developer.mozilla.org/en-US/docs/Web/API/Performance) 来测量这些数字的。我在 `renderTopicListItem` 的开头添加了 `performance.mark("rtli-start")`，然后在结尾添加了 `performance.measure("rtli", "rtli-start")`。

然后，我 **关闭开发者工具并禁用浏览器插件** （开发者工具和浏览器插件会显著影响渲染性能）重新加载浏览器。加载完成后，打开开发者工具并运行此代码来汇总测量结果：

```javascript
performance.getEntriesByName("rtli").reduce((v, m) => v + m.duration, 0);

```

* * *

我们肯定会合并这项更改——这显然是一个更好的实现。但我不太确定它是否会给我们带来可见的渲染性能差异 🤔

---

<div class="post-metadata">

### Author: ![rrit](https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/32.png) [@rrit](https://meta.discourse.org/u/rrit)
#### Post date: [2022年一月5日 10:05 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/9 "2022-01-05T10:05:06Z")

</div>

我仍然可以在 Firefox 的隐私模式（Linux）中使用性能 API 重现性能优势。

正在测试 [http://localhost:4200/latest](http://localhost:4200/latest)  
`renderTopicListItem` 的耗时从约 290 毫秒降至约 190 毫秒。

我的 Discourse 测试实例包含许多带有许多回复和许多不同作者的主题 - 数据是从生产实例中提取的。这导致需要渲染大量元素。  
也许这是我们基准测试的差异所在？

* * *

## 视口下方内容的预渲染

Discourse 在“latest”页面上预渲染 30 个主题。然后内容首次显示（FCP）。视口上方只有约 12 个主题可见。

主题页面也一样：预渲染 20 篇帖子，但视口上方最多只显示 6 篇单行帖子。

这可能是 FCP 的另一个优化点。

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2022年一月5日 11:56 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/10 "2022-01-05T11:56:25Z")

</div>

> [@rrit](#):
>
> Firefox 私密模式（Linux）。

您介意分享一下 Firefox 和操作系统版本吗？290 毫秒的数字几乎是 2018 年 Android 设备的 3 倍，这有点令人惊讶。

> [@rrit](#):
>
> 这导致需要渲染大量元素。  
> 也许这就是我们基准测试的差异？

是的，这也许能解释一些差异。在我的例子中，我使用来自 Meta 的实时数据运行了它们：

```plaintext
bin/ember-cli --environment production --proxy https://meta.discourse.org

```

> [@rrit](#):
>
> 预渲染“below-the-fold”的内容

是的，这是一个可能的改进。但是，我们需要非常小心，确保布局和/或滚动不会跳动（例如，如果用户在滚动到一半时刷新页面）。“below the fold”的定义也因设备/浏览器/主题而异。

---

<div class="post-metadata">

### Author: ![rrit](https://avatars.discourse-cdn.com/v4/letter/r/b5ac83/32.png) [@rrit](https://meta.discourse.org/u/rrit)
#### Post date: [2022年一月5日 13:28 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/11 "2022-01-05T13:28:00Z")

</div>

## 代理到 [meta.discourse.org](http://meta.discourse.org)

遗憾的是，使用代理运行 ember 对我来说失败了：

```bash
d/ember-cli --environment production --proxy https://meta.discourse.org

```

[http://localhost:4200/](http://localhost:4200/)

```plaintext
Discourse Build Error

Error [ERR_TLS_CERT_ALTNAME_INVALID]: 主机名/IP 与证书的 altnames 不匹配：
主机：localhost. 不在证书的 altnames 中：DNS:*.cdck-prod-meta.discourse.cloud

```

[http://127.0.0.1:4200/](http://127.0.0.1:4200/)

```plaintext
Discourse Build Error

Error [ERR_TLS_CERT_ALTNAME_INVALID]: 主机名/IP 与证书的 altnames 不匹配：
主机：meta.discourse.org. 不在证书的 altnames 中：DNS:*.cdck-prod-meta.discourse.cloud

```

* * *

### 用于基准测试的系统

摘自 Firefox `about:support`

| 名称 | Firefox |
| --- | --- |
| 版本 | 95.0.2 |
| Build-ID | 20211219102529 |
| Distributions-ID | canonical-002 |
| 用户代理 | Mozilla/5.0 (X11; Linux x86\_64; rv:95.0) Gecko/20100101 Firefox/95.0 |
| 操作系统 | Linux 5.10.0-0.bpo.9-amd64 #1 SMP Debian 5.10.70-1~bpo10+1 (2021-10-10) |
| 操作系统主题 | Adwaita-dark / Adwaita |
| 应用程序文件 | /snap/firefox/777/usr/lib/firefox/firefox |

| 名称 | Firefox Developer Edition |
| --- | --- |
| 版本 | 96.0b10 |
| Build-ID | 20211228195952 |
| Update-目录 | /opt/firefox-dev-autoinstall |
| Update-通道 | aurora |
| 用户代理 | Mozilla/5.0 (X11; Linux x86\_64; rv:96.0) Gecko/20100101 Firefox/96.0 |
| 操作系统 | Linux 5.10.0-0.bpo.9-amd64 #1 SMP Debian 5.10.70-1~bpo10+1 (2021-10-10) |
| 操作系统主题 | Adwaita-dark / Adwaita |
| 应用程序文件 | /opt/firefox-dev-autoinstall/firefox-bin |

摘自 Chromium chrome://system/

| CHROME VERSION | 90.0.4430.212 built on Debian 10.9, running on Debian 10.11 |
| --- | --- |
| OS VERSION | Linux: 5.10.0-0.bpo.9-amd64 |

**操作系统版本：**

```plaintext
# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian

```

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2022年一月5日 22:23 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/12 "2022-01-05T22:23:43Z")

</div>

重构的拉取请求现已合并：

> <https://github.com/discourse/discourse/commit/e6ab8f5b71efec9712b66806915a1f79d1e5998a>
>
> da6edc1 introduced the \`lookupView\` method, which initialized a fresh resolver, …and used it to directly look up raw-views (with no caching). This worked well, but was not a clean solution. It required initializing an entirely new resolver, and did not have any caching.
> 
> This commit updates the \`helperContext\` to include access to the registry, and uses it to perform raw-view lookups. As well as re-using the registry, this also means we're making use of the resolver's built-in cache.
> 
> I haven't been able to measure any noticeable performance impact from this change, but there is certainly less work being done, so it may be beneficial on older devices.
> 
> Co-authored-by: Ayke Halder \<rr-it@users.noreply.github.com\>

感谢你提出这个问题，@rrit - 这是一个很好的改进！

---

<div class="post-metadata">

### Author: ![david](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/david/32/157490_2.png) [@david](https://meta.discourse.org/u/david)
#### Post date: [2022年一月6日 08:00 UTC](https://meta.discourse.org/t/optimizing-fcp-lcp-by-caching-raw-view-module-lookups/213759/13 "2022-01-06T08:00:36Z")

</div>

此主题已在 9 小时后自动关闭。不再允许回复。
