跳到主要内容

性能

开发人员经常询问优化性能的策略电子应用。软件工程师、消费者和框架开发人员不要总是对“性能”的含义有一个统一的定义。这个该文件概述了一些电子维修人员最喜欢的减少使用的内存、CPU和磁盘资源量,同时确保应用程序能够响应用户输入并尽快完成操作可能。此外,我们希望所有绩效策略保持较高应用程序安全的标准。

关于如何使用JavaScript构建高性能网站的知识和信息通常也适用于Electron应用程序。在一定程度上,资源讨论如何构建高性能Node.js应用程序也适用,但仔细理解术语“性能”对Node.js后端,而不是客户端上运行的应用程序的后端。

此列表是为方便您而提供的,与我们的安全检查表–并非详尽无遗。这很可能按照下面列出的所有步骤构建一个慢速Electron应用程序。电子是一个强大的开发平台,使您作为开发人员能够做更多的事情或者更少你想要的。所有这些自由意味着性能在很大程度上你的责任。

测量,测量,测量

下面的列表包含一些相当简单的步骤易于实施。然而,构建最具性能的应用程序版本需要您超越多个步骤。相反,你必须通过仔细分析和测量。瓶颈在哪里?当用户单击按钮时行动在时间上首当其冲?虽然应用程序只是处于空闲状态对象占用的内存最多?

我们一次又一次地看到一个高性能的Electron应用程序可以分析运行中的代码,找到最多资源匮乏,并对其进行优化一次又一次的艰苦过程将显著增加你的应用程序性能。使用Visual Studio Code或Slack已经证明,这种做法是迄今为止最可靠的战略提高性能。

要了解有关如何评测应用程序代码的更多信息,请熟悉Chrome开发工具。对于查看多个流程的高级分析立即考虑铬追踪工具。

清单:绩效建议

你的应用程序可能会更精简、更快,而且通常更少如果你尝试这些步骤,那就太吝啬了。

  1. 粗心地包括模块
  2. 加载和运行代码太早
  3. 阻塞主进程
  4. 阻止渲染器进程
  5. 不必要的聚合物填充物
  6. 不必要或阻塞的网络请求
  7. 捆绑代码
  8. 呼叫Menu.setApplicationMenu(null)当您不需要默认菜单时

1.粗心地包含模块

在将Node.js模块添加到应用程序之前,请检查所述模块。怎么该模块包含许多依赖项吗?什么样的资源只需在要求()声明?你可能会发现在NPM包注册表上下载次数最多的模块或GitHub上最多的星星实际上并不是最瘦或最小的。

为什么?

该建议背后的理由最好用真实世界来说明例子。在Electron的早期,可靠的网络检测连接是一个问题,导致许多应用程序使用的模块公开了简单的在线()方法。

该模块通过尝试连接到已知端点的数量。对于这些端点的列表,它取决于另一个模块,其中还包含一个已知端口列表。这个依赖项本身依赖于包含端口信息的模块,该模块它以JSON文件的形式出现,内容超过100000行。每当加载模块时(通常在需要(“模块”)声明),它将加载所有依赖项,并最终读取和解析此JSON文件。解析数千行JSON是一项非常昂贵的操作。打开一台速度较慢的机器,它可以占用整整几秒钟的时间。

在许多服务器上下文中,启动时间实际上是无关紧要的。Node.js服务器需要有关所有端口的信息实际上可能“性能更高”如果它在服务器启动时将所有必需的信息加载到内存中更快地提供请求的好处。本例中讨论的模块是不是“坏”模块。然而,Electron应用程序不应进行加载、解析和在内存中存储它实际上不需要的信息。

简而言之,一个主要为Node.js服务器编写的看似优秀的模块运行Linux可能会对应用程序的性能带来坏消息。在这方面例如,正确的解决方案是根本不使用模块,而是使用更高版本的Chromium中包含连接检查。

怎么用?

考虑模块时,我们建议您检查:

  1. 包含的依赖项的大小
  2. 加载所需的资源(要求())它
  3. 执行您感兴趣的操作所需的资源

可以为加载模块生成CPU配置文件和堆内存配置文件使用命令行上的单个命令。在下面的示例中,我们将看到流行的模块请求.

节点--cpu-prof--heap-prof-e“require('request')”

执行此命令会导致.cprofile文件文件和.heapprofile文件文件。这两个文件都可以使用Chrome开发工具,使用性能存储器选项卡分别是。

性能CPU配置文件

性能堆内存配置文件

在这个例子中,在作者的机器上,我们看到加载请求几乎半秒钟,而节点提取占用的内存大大减少小于50ms。

2.加载和运行代码太快

如果您有昂贵的设置操作,请考虑推迟这些操作。检查所有应用程序启动后立即执行的工作。而不是开火立即停止所有操作,考虑将它们按顺序错开与用户的旅程紧密结合。

在传统的Node.js开发中,我们习惯于要求()顶部的语句。如果您当前正在编写Electron应用程序使用相同的策略正在使用您没有使用的大型模块立即需要,应用相同的策略并将加载延迟到恰逢其时。

为什么?

加载模块是一项花费惊人的操作,尤其是在Windows上。当你的应用程序启动时,它不应该让用户等待目前没有必要。

这似乎很明显,但许多应用程序往往会执行大量在应用程序启动后立即工作,比如检查更新,下载稍后流中使用的内容,或执行重磁盘I/O操作。

让我们以Visual Studio代码为例。当你打开一个文件时,它会立即向您显示该文件,而不突出显示任何代码,划分优先级你与文本互动的能力。一旦它完成了这项工作,它就会继续代码高亮显示。

怎么用?

让我们考虑一个示例,假设您的应用程序正在解析文件在虚构的.foo文件格式。为此,它依赖于同样虚构的foo-parser模块。在传统的Node.js开发中,您可以编写急切加载依赖项的代码:

解析器.js
常数英尺= 要求('节点:fs')
常数fooParser程序= 要求(“foo-parser”)

分析器 {
建造师 () {
.文件夹 =英尺.读目录同步('.')
}

获取解析文件 () {
返回fooParser(foo分析器).解析(.文件夹)
}
}

常数解析器= 新的 分析器()

模块.出口 = {解析器}

在上面的例子中,我们正在做很多工作,这些工作正在尽快执行加载文件时。我们需要立即获取已解析的文件吗?我们可以吗稍后再做这项工作获取解析文件()实际被调用了吗?

解析器.js
//“fs”可能已经被加载了,所以`require()`调用很便宜
常数英尺= 要求('节点:fs')

分析器 {
异步 获取文件 () {
//调用“getFiles”后尽快触摸磁盘。
//此外,确保我们不会使用
//异步版本。
.文件夹 = .文件夹 || 等待英尺.承诺.读目录('.')

返回 .文件夹
}

异步 获取解析文件 () {
//我们虚构的foo-parser是一个需要加载的大型且昂贵的模块,因此
//推迟这项工作,直到我们真正需要解析文件。
//由于`require()`附带模块缓存,因此`require()`调用
//将只花费一次-后续调用`getParsedFiles()`
//会更快。
常数fooParser程序= 要求(“foo-parser”)
常数文件夹= 等待 .获取文件()

返回fooParser程序.解析(文件夹)
}
}

//这个操作现在比我们前面的例子便宜很多
常数解析器= 新的 分析器()

模块.出口 = {解析器}

简而言之,“及时”分配资源,而不是全部分配当你的应用程序启动时。

3.阻塞主进程

Electron的主要流程(有时称为“浏览器流程”)很特殊:应用程序所有其他进程的父进程和主进程操作系统与交互。它处理窗口、交互和应用程序内各个组件之间的通信。它还容纳了UI线程。

在任何情况下,都不应使用长时间运行的操作。阻止UI线程意味着你的整个应用程序将冻结,直到主进程准备好继续处理。

为什么?

主进程及其UI线程本质上是主进程的控制塔应用程序内部的操作。当操作系统告诉您的应用程序有关鼠标单击,它将在到达窗口之前完成主进程。如果您的窗口正在渲染一个奶油色的平滑动画,则需要与关于这一点的GPU流程–再次经历主要流程。

Electron和Chromium小心地进行繁重的磁盘I/O和CPU绑定操作以避免阻塞UI线程。你也应该这样做。

怎么用?

Electron强大的多进程架构随时为您提供帮助您的长时间运行任务,但也包括少量性能陷阱。

  1. 对于长时间运行的CPU负载任务,请使用工作者线程,考虑将它们移动到BrowserWindow,或者(作为最后的手段)生成一个专用进程。

  2. 避免使用同步IPC和@电子/遥控模块数量相同尽可能地。虽然有合法的用例,但很容易不知不觉地阻塞UI线程。

  3. 避免在主进程中使用阻塞I/O操作。简而言之,无论何时核心Node.js模块(如英尺子进程(_P))提供同步或异步版本,您应该首选异步和非阻塞变体。

4.阻止渲染器进程

由于Electron附带了当前版本的Chrome,您可以使用Web平台提供的最新和最棒的功能,可延迟或卸载繁重的任务以确保应用程序平稳响应的方式进行操作。

为什么?

您的应用程序可能有很多JavaScript要在渲染器进程中运行。这个诀窍是尽可能快地执行操作而不带走保持滚动流畅、响应用户输入或动画所需的资源每秒60帧。

在渲染器的代码中编排操作流是如果用户有时抱怨你的应用程序“结巴”,这一点特别有用。

怎么用?

一般来说,所有关于构建现代高性能web应用程序的建议浏览器也适用于Electron的渲染器。您的两个主要工具目前正在处理requestIdleCallback()用于小型操作和网络工作者用于长时间运行的操作。

requestIdleCallback()允许开发人员将要进程进入空闲期后立即执行。它使您能够在不影响用户体验的情况下执行低优先级或背景工作。有关如何使用它的更多信息,查看MDN上的文档.

网络工作者是在单独的线程上运行代码的强大工具。需要考虑的一些注意事项–请咨询Electron的多线程文档Web Workers的MDN文档。它们是理想的解决方案对于任何需要大量CPU电源的长时间操作时间。

5.不必要的聚合物填充物

Electron的一大优点是,您可以准确地知道哪种发动机将解析JavaScript、HTML和CSS。如果您正在重新提交代码为整个web编写,确保不包含polyfill功能电子。

为什么?

在为当今互联网构建web应用程序时,最古老的环境规定哪些功能可以使用,哪些功能不能使用。即使Electron支持性能良好的CSS过滤器和动画,而较旧的浏览器可能不会。在哪里?你可以使用WebGL,你的开发人员可能已经选择了一个更缺乏资源的支持旧手机的解决方案。

当谈到JavaScript时,您可能已经包含了工具包库,如jQuery用于DOM选择器或polyfill,如再生器运行时间支持异步/等待.

基于JavaScript的polyfill很少比同等的polyfill更快Electron中的原生特征。不要通过发送您的自己版本的标准web平台功能。

怎么用?

假设聚合物填充当前版本的Electron是不必要的。如果您有疑问,请检查caniuse.com网站并检查电子版中使用的铬版本支持您想要的功能。

此外,请仔细检查您使用的库。它们真的有必要吗?jQuery(jQuery)例如,它取得了巨大的成功,其许多功能现在都是其中的一部分可用的标准JavaScript功能集.

如果您正在使用类似TypeScript的转发器/编译器,请检查其配置并确保您的目标是支持的最新ECMAScript版本电子。

6.不必要或阻塞的网络请求

如果可能的话,避免从互联网上获取很少改变的资源与应用程序捆绑在一起。

为什么?

许多Electron用户从一个完全基于网络的应用程序开始变成桌面应用程序。作为web开发人员,我们习惯于加载来自各种内容交付网络的资源。现在你是运送适当的桌面应用程序,尽可能“切断电源线”并避免让您的用户等待永远不会改变并且可能改变的资源轻松包含在应用程序中。

一个典型的例子是谷歌字体。许多开发人员利用谷歌的令人印象深刻的免费字体集合,随附内容交付网络。宣传很简单:包括几行CSS和谷歌会照顾其他人。

构建Electron应用程序时,如果您下载字体并将其包含在应用程序包中。

怎么用?

在理想情况下,您的应用程序不需要网络来运行全部。要做到这一点,你必须了解你的应用程序正在下载哪些资源-以及这些资源有多大。

为此,请打开开发人员工具。导航到网络标签和检查这个禁用缓存选项。然后,重新加载渲染器。除非你的应用程序禁止这样的重装,你通常可以通过点击触发重装命令+RCtrl+R组合键重点关注开发人员工具。

这些工具现在将仔细记录所有网络请求。在第一次传球中,对正在下载的所有资源进行评估,重点放在较大的文件上第一。其中是否有不更改的图像、字体或媒体文件,以及可以包含在您的捆绑包中吗?如果是,请将其包括在内。

下一步,启用网络节流。查找当前读取在线的并选择较慢的速度,例如快速3G.重新加载您的渲染器并查看应用程序是否有任何不必要的资源正在等待。在许多情况下,应用程序会等待网络请求完成尽管实际上并不需要所涉及的资源。

作为提示,从Internet加载您可能想要更改的资源不提供应用程序更新是一种强大的策略。对于高级控制资源加载方式,考虑投资服务工人.

7.捆绑您的代码

正如已经指出的那样"加载和运行代码太早",打电话要求()是一项昂贵的手术。如果你能做到这一点,将应用程序的代码绑定到单个文件中。

为什么?

现代JavaScript开发通常涉及许多文件和模块。While期间这对于使用Electron开发来说非常好,我们强烈建议您将所有代码捆绑到一个文件中,以确保开销包括在通话中要求()只在加载应用程序时支付一次。

怎么用?

现在有很多JavaScript捆绑包,我们很清楚通过推荐一种工具而非另一种工具来激怒社区。然而,我们确实如此建议您使用能够处理Electron独特需要同时处理Node.js和浏览器环境的环境。

在撰写本文时,流行的选择包括网络包,包裹、和rollup.js文件.

8.电话Menu.setApplicationMenu(空)当您不需要默认菜单时

Electron将在启动时使用一些标准条目设置默认菜单。但是,您的应用程序可能会希望改变这一点,这将有助于提高启动性能。

为什么?

如果您构建自己的菜单或使用没有本机菜单的无框架窗口,您应该尽早告诉Electron不要设置默认菜单。

怎么用?

呼叫Menu.setApplicationMenu(空)之前app.on(“就绪”)。这将阻止Electron设置默认菜单。另请参见https://github.com/electron/elecron/issues/35512进行相关讨论。