【问题标题】:Does Google Closure Compiler ever decrease performance?Google Closure Compiler 是否会降低性能?
【发布时间】:2026-02-13 13:25:05
【问题描述】:

我正在编写 Google Chrome 扩展程序。由于 JavaScript 文件是从磁盘加载的,它们的大小几乎不重要。

我一直在使用 Google Closure Compiler,因为显然它可以优化性能并减少代码大小。

但我在 Closure Compiler 的输出顶部注意到了这一点:

var i = true, m = null, r = false;

这样做的目的显然是减少文件大小(整个脚本中所有后续使用true/null/false 都可以替换为单个字符)。

但这肯定会对性能造成轻微影响吗?只读取文字 true 关键字肯定比按名称查找变量并发现其值为 true...?

这种性能下降值得担心吗? Google Closure Compiler 是否还有其他可能会减慢执行速度的方法?

【问题讨论】:

  • 我怀疑对性能的影响很大,但文件大小的减少肯定会超过它。
  • 我确信真正的答案在于 v8 或任何其他 js JIT 是否进行全局常量传播和折叠(我确信他们在一定程度上这样做)
  • 使用“GCC”作为“Google Closure Compiler”的缩写可能会导致混淆。 gcc.gnu.org
  • 这个问题不再实际了,因为编译器现在使用!0 代表true!1 代表false
  • @RokKralj,仍然有效!0 肯定比 true 慢。

标签: javascript optimization google-closure-compiler


【解决方案1】:

答案是也许

让我们看看关闭团队是怎么说的。

From the FAQ:

编译器是否在我的应用程序的执行速度和下载代码大小之间进行权衡?

是的。任何优化编译器都会做出权衡。 一些尺寸优化确实会引入少量的速度开销。但是,Closure Compiler 的开发人员一直小心翼翼地不引入显着的额外运行时。 某些编译器的优化甚至会缩短运行时间(请参阅下一个问题)。

编译器是否针对速度进行了优化?

在大多数情况下,较小的代码是更快的代码,因为下载时间通常是 Web 应用程序中最重要的速度因素。减少冗余的优化也加快了代码的运行时间。

我断然挑战他们在这里所做的第一个假设。使用的变量名称的大小不会直接影响各种 JavaScript 引擎如何处理代码——事实上,JS 引擎并不关心你是否将变量称为 supercalifragilisticexpialidociousx(但我作为程序员肯定会这样做) .如果您担心交付,下载时间是最重要的部分 - 运行缓慢的脚本可能是由数以百万计的事情引起的,我怀疑该工具根本无法解决这些问题。

要如实理解您的问题是什么,您首先需要问的是“是什么让 JavaScript 变快或变慢?”

然后我们当然会遇到问题,“我们在谈论什么 JavaScript 引擎?”

我们有:

  • 卡拉坎(歌剧)
  • 脉轮 (IE9+)
  • SpiderMonkey (Mozilla/FireFox)
  • SquirrelFish(Apple 的 webkit)
  • V8(铬)
  • Futhark(歌剧)
  • JScript(9 之前的所有 IE 版本)
  • JavaScriptCore(Konqueror、Safari)
  • I've skipped out on a few

这里有人真的认为他们的工作方式都一样吗?尤其是 JScript 和 V8?见鬼!

同样,当 google 闭包编译代码时,它是为哪个引擎构建东西的?你觉得幸运吗?

好的,因为我们永远不会涵盖所有这些基础,所以让我们试着在这里更笼统地看一下“旧”与“新”代码。

这是来自one of the best presentations on JS Engines I've ever seen 的此特定部分的快速摘要。

较旧的 JS 引擎

  • 代码被直接解释和编译成字节码
  • 没有优化:你得到你得到的
  • 由于语言类型松散,代码难以快速运行

新的 JS 引擎

  • 引入即时 (JIT) 编译器以实现快速执行
  • 为真正快速的代码引入类型优化 JIT 编译器(考虑接近 C 代码速度)

这里的主要区别在于新引擎引入了 JIT 编译器。

从本质上讲,JIT 会优化您的代码执行,使其运行得更快,但如果发生它不喜欢的事情,它会转身再次变慢。

你可以通过这样的两个函数来做这样的事情:

var FunctionForIntegersOnly = function(int1, int2){
    return int1 + int2;
}

var FunctionForStringsOnly = function(str1, str2){
    return str1 + str2;
}

alert(FunctionForIntegersOnly(1, 2) + FunctionForStringsOnly("a", "b"));

通过 google 闭包运行它实际上将整个代码简化为:

alert("3ab");

从书中的每一个指标来看,这都要快得多。这里真正发生的是它简化了我非常简单的示例,因为它做了一些部分执行。但是,这是您需要小心的地方。

Lets say we have a y-combinator in our code,编译器会变成这样:

(function(a) {
 return function(b) {
    return a(a)(b)
  }
})(function(a) {
  return function(b) {
    if(b > 0) {
      return console.log(b), a(a)(b - 1)
    }
  }
})(5);

不是真的更快,只是缩小了代码。

JIT 通常会看到,在实践中,您的代码只需要向该函数输入两个字符串,并返回一个字符串(或第一个函数的整数),这会将其放入特定于类型的 JIT,这使得它真正快的。现在,如果 google 闭包做了一些奇怪的事情,比如将具有几乎相同签名的两个函数转换为一个函数(对于非平凡的代码),如果编译器做了 JIT 不喜欢的事情,你可能会失去 JIT 速度。

那么,我们学到了什么?

  • 您可能有经过 JIT 优化的代码,但编译器会将您的代码重新组织成其他内容
  • 旧版浏览器没有 JIT,但仍然可以运行您的代码
  • 闭包编译的 JS 通过部分执行简单函数的代码来调用更少的函数。

那你怎么办?

  • 编写小而中肯的函数,编译器将能够更好地处理它们
  • 如果您对 JIT 有非常深入的了解、手动优化代码并使用了这些知识,那么闭包编译器可能不值得使用。
  • 如果您希望代码在旧版浏览器上运行得更快一些,它是一个很好的工具
  • 权衡取舍通常是值得的,但请仔细检查,不要一直盲目相信它。

一般来说,您的代码更快。您可能会引入各种 JIT 编译器不喜欢的东西,但如果您的代码使用较小的函数和正确的原型面向对象设计,它们将很少见。如果您考虑编译器正在执行的全部范围(更短的下载和更快的执行),那么像var i = true, m = null, r = false; 这样的奇怪事情可能是编译器做出的值得权衡的代价,即使它们运行速度较慢,总寿命更快。

值得一提的是,Web 应用程序执行中最常见的瓶颈是文档对象模型,如果你的代码很慢,我建议你在这方面付出更多的努力。

【讨论】:

  • @John 我完全理解该声明,但我不确定我在哪里不清楚。我确实说过“如果您担心交付,下载时间是最重要的部分——运行缓慢的脚本可能是由数百万我怀疑该工具根本无法解决的事情引起的。”你能指出来让我解决吗?我也在文章末尾提到了 DOM。
  • 很好的答案,但我不禁担心 JIT 编译器可能会在内联和部分评估“常量”值时遇到更多麻烦,如果它在一个变量中,可以想象以后可以重新分配。
  • @incognito:引用:“我完全挑战他们在这里所做的第一个假设。变量名称的大小......”我们没有做出这个假设。你从哪里得到闭包编译器将变量名大小与运行时性能等同起来的想法?但是,应用程序的启动时间与下载代码所需的时间高度相关,并且(可悲的是)每个字节都很重要。这一事实在过去十年中变得更糟,因为移动平台因其高延迟/低带宽/低可靠性连接、低于标准的处理器和微小的浏览器缓存而变得重要。
  • 我应该澄清一下,除非有奇怪的开始,否则总体评估是合理的,但我要补充一点,因为有不少于 4 个主要引擎(每个引擎一个的主要浏览器,不考虑即将推出的引擎)每个都有不同的特点
  • @Incognito,您的倒数第二段指出“总寿命更快”。这里的“总寿命”指的是什么?
【解决方案2】:

似乎在现代浏览器中,使用文字 truenull 与变量相比,几乎在所有情况下都完全没有区别(如零;它们完全相同)。在极少数情况下,变量实际上更快。

因此,节省的那些额外字节是值得的,而且没有任何成本。

true 与变量 (http://jsperf.com/true-vs-variable):

null 与变量 (http://jsperf.com/null-vs-variable):

【讨论】:

  • 在极少数情况下,变量实际上更快。解释一下,这有什么意义?
  • @Pacerier 不知道。这只是数据(在 jsperf 上可用)。
  • 那么这证明我们没有衡量我们认为我们正在衡量的东西。某处出了点问题,不应该相信时间安排。
【解决方案3】:

我认为会有非常轻微的性能损失,但在较新的现代浏览器中不太重要。

请注意,闭包编译器的标准别名变量都是全局变量。这意味着,在 JavaScript 引擎需要线性时间来导航功能范围(例如 IE

此外,除了赋值或参数之外,实际上不应该在很多地方直接在编译代码中看到“true”、“false”或“null”。例如:if (someFlag == true) ... 大多只是写成if (someFlag) ...,由编译器编译成a && ...。您大多只在赋值 (someFlag = true;) 和参数 (someFunc(true);) 中看到它们,而这种情况实际上并不经常发生。

结论是:尽管许多人怀疑 Closure Compiler 的标准别名(包括我在内)的有用性,但您不应该期望任何实质性的性能损失。不过,您也不应该期望压缩后的大小会带来任何实质性的好处。

【讨论】:

  • 仅供参考:“if (someFlag === true) ...”未折叠为“a && ...”。通常,闭包编译器在不知道变量可以保存的值的情况下进行这种重写,并且“a === true”只有在其唯一可能的值是“true”时才能变成简单的“a”,所以 -称为假值(假、空、未定义等)。
  • @John,你是对的。 if (someFlag) ... 被折叠成a && ...,但=== true== true 被折叠成a === true && ... 我会编辑我的答案。谢谢!