V8开发人员在这里。考虑到对这个问题的浓厚兴趣,并且缺乏其他答案,我可以试一试。恐怕这不是您想要的答案。
在打包SMI阵列的世界中,例如,在编程方面有一些准则吗?
简短的答案:就在这里:const guidelines = ["keep your integers small enough"]
。
更长的答案:出于各种原因,很难提供一套全面的指南。通常,我们认为JavaScript开发人员应该编写对他们及其用例有意义的代码,而JavaScript引擎开发人员应弄清楚如何在其引擎上快速运行该代码。另一方面,从某种意义上来说,显然存在一些局限性,因为某些编码模式将始终比其他编码模式具有更高的性能成本,而与引擎实现选择和优化工作无关。
当我们谈论性能建议时,我们会牢记这一点,并仔细评估哪些建议在许多引擎和许多年中都很有可能保持有效,并且是相当惯用的/非侵入性的。
回到前面的示例:内部使用Smis应该是用户代码不需要知道的实现细节。这样可以使某些情况下更有效,在其他情况下也不应受到伤害。并非所有引擎都使用Smis(例如,AFAIK Firefox / Spidermonkey历史上从未使用过;我听说有些情况下,这些天确实在使用Smis;但是我不知道任何细节,也无法与任何权威人士交谈。事情)。在V8中,Smis的大小是内部细节,实际上随着时间和版本的变化而变化。在曾经是大多数使用案例的32位平台上,Smis始终是31位带符号整数;在64位平台上,它们曾经是32位有符号整数,最近似乎是最常见的情况,直到在Chrome 80中我们发布了“指针压缩” 对于64位架构,需要将Smi大小降低到32位平台已知的31位。如果您碰巧基于Smis通常为32位的假设来实现,则可能会遇到诸如这个。
幸运的是,正如您所指出的,双数组仍然非常快。对于大量数字代码,假定/定位双精度数组可能很有意义。鉴于JavaScript中使用double的情况很普遍,可以合理地假设所有引擎都对double和double数组有良好的支持。
是否可以使用Javascript进行通用的高性能编程,而无需使用诸如宏系统之类的工具将vec.add()内联到调用站点中?
“通用”通常与“高性能”不符。这与JavaScript或特定的引擎实现无关。
“通用”代码意味着必须在运行时做出决定。每次执行一个函数时,都必须运行代码来确定“是x
整数?如果是,则采用该代码路径。是x
字符串吗?然后跳过这里。是否是对象?它有.valueOf
吗?否?然后也许.toString()
?也许在它的原型链上?调用它,然后从头开始重新生成结果”。“高性能”优化代码本质上是基于删除所有这些动态检查的想法而构建的;只有当引擎/编译器可以提前某种方式推断类型时,这种方法才有可能:如果它可以证明(或以足够高的概率假设)x
总是整数,那么只需要为这种情况生成代码(如果涉及未经证实的假设,则通过类型检查加以保护)。
内联与所有这些正交。仍然可以内联“通用”函数。在某些情况下,编译器可能能够将类型信息传播到内联函数中,以减少那里的多态性。
(作为比较:C ++是一种静态编译语言,具有用于解决相关问题的模板。简而言之,它们可以让程序员明确指示编译器创建在给定类型上参数化的函数(或整个类)的专用副本。在某些情况下是一个不错的解决方案,但并非没有缺点,例如编译时间长和二进制文件大,JavaScript当然没有模板之类的东西,您可以eval
用来构建一个有点相似的系统,但是也会遇到类似的缺点:您必须在运行时完成C ++编译器的工作,并且您不得不担心所生成的代码量巨大。)
鉴于宏调用站点和优化问题,如何将高性能代码模块化到库中?例如,如果我愉快地高速使用线性代数软件包A,然后导入依赖于A的软件包B,但是B用其他类型对其进行调用并对其进行了优化,突然(无需更改代码)我的代码运行速度就会变慢。
是的,这是JavaScript的普遍问题。V8曾经Array.sort
在内部用JavaScript 实现某些内置函数(如),而这个问题(我们称之为“类型反馈污染”)是我们完全放弃该技术的主要原因之一。
就是说,对于数字代码而言,类型并不多(只有Smis和doubles),并且正如您所指出的,它们在实践中应该具有相似的性能,因此,尽管类型反馈污染确实是理论上的关注点,但在某些情况下可以产生重大影响,在线性代数方案中,您很有可能看不到可测量的差异。
而且,在发动机内部,除了“一种类型==快”和“一种以上==慢”之外,还有更多的情况。如果给定的操作同时看到了Smis和两倍,那完全没问题。从两种数组中加载元素也是可以的。当负载看到了太多不同的类型以至于不再单独跟踪它们时,我们使用术语“巨形”来代替,而是使用一种更通用的机制来更好地缩放到大量类型,包含此类负载的函数可以仍然得到优化。“非优化”是一种非常具体的行为,必须丢弃某个函数的优化代码,因为看到的是以前从未见过的新类型,因此优化代码无法处理。但这还好:只需返回未优化的代码以收集更多的类型反馈,然后稍后再进行优化。如果这种情况发生了两次,则无需担心。它仅在病态严重的情况下才成为问题。
因此,所有内容的摘要是:不必担心。只需编写合理的代码,让引擎处理它即可。所谓“合理”,是指:对您的用例有意义的,可读的,可维护的,使用高效的算法,不包含诸如读取数组长度之外的错误。理想情况下,仅此而已,您无需执行其他任何操作。如果您觉得做某件事更好,和/或您实际上在观察性能问题,我可以提供两个想法:
使用TypeScript 可以提供帮助。大提示:TypeScript的类型针对开发人员的生产力,而不是执行性能(事实证明,这两种观点与类型系统的要求非常不同)。也就是说,存在一些重叠:例如,如果您始终将注释为number
,则TS编译器会在您不小心将null
本应仅包含/操作于数字的数组或函数中时警告您。当然,仍然需要纪律:单个number_func(random_object as number)
逃生舱口可以无声地破坏所有内容,因为类型注释的正确性没有在任何地方强制执行。
使用TypedArrays也可以提供帮助。与常规的JavaScript数组相比,它们每个数组的开销(内存消耗和分配速度)要多一些(因此,如果您需要许多小数组,那么常规数组可能会更有效率),并且灵活性较差,因为它们无法增长或分配后缩小,但是它们确实提供了所有元素完全具有一种类型的保证。
是否有任何易于使用的良好测量工具来检查Javascript引擎在内部对类型进行的操作?
不,这是故意的。如上所述,我们不希望您专门针对V8今天可以特别优化的任何模式量身定制代码,我们也不相信您也确实希望这样做。这组事情可以朝任一方向改变:如果有一种您想使用的模式,我们可能会在将来的版本中对此进行优化(我们以前曾想过将未装箱的32位整数存储为数组元素的想法。 ,但有关工作尚未开始,因此没有承诺);有时,如果过去有一种我们用来进行优化的模式,那么如果它妨碍了其他更重要/更具影响力的优化,我们可能会决定放弃它。另外,众所周知,内联启发法很难解决,因此,在正确的时间做出正确的内联决策是一个正在进行的研究领域,并且是对引擎/编译器行为的相应更改;这又使每个人都不幸(您和我们),如果您花费大量时间来调整代码,直到某些当前浏览器版本做出了您认为(或知道?)的最佳内联决策,那么半年后才回来,以了解当时的浏览器改变了他们的试探法。
当然,您始终可以衡量整个应用程序的性能-这才是最重要的,而不是内部引擎专门做出的选择。当心微基准测试,因为它们会误导您:如果您仅提取两行代码并对它们进行基准测试,那么情况很可能会完全不同(例如,不同的类型反馈),从而引擎将做出非常不同的决策。