作者:云凰杯青欢迎加入Wasm与emscripten技术交流群,群聊号:939206522。这是关于Emscripten的系列文章。更多文章,请查看下方链接。Emscripten代码移植系列文章Emscripten代码移植主题系列是emscripten中文站点的一部分。本文为第一题。第一篇介绍代码的可移植性和局限性第二篇介绍Emscripten的运行环境第三篇第一篇介绍连接C++和JavaScript第三篇第二篇介绍embind第四篇介绍文件和文件系统第六篇介绍Emscripten如何调试代码翻译地址原文地址一般来说,你只需要编译运行你的代码,无需优化。一旦您可以让您的代码正确运行,您就可以使用本文中的技术来使您的代码加载和运行得更快。1.如何优化代码使用emcc时,通过指定优化标志进行优化。有不同级别的优化,即:-O0、-O1、-O2、-Os、-Oz、-O3。例如,下面的代码使用-O2级别emcc-O2file.cpp进行了优化和编译,随着优化级别的提高,逐渐引入更激进的优化方法,从而获得更高的性能和更大的代码大小,代价是减少编译时间增加。优化项也会导致一些问题。那么,什么时候使用哪个级别的优化?第一次移植代码时,默认运行emcc,不优化。检查您的代码是否有效并修复问题。在开发中,低级优化用于缩短编译迭代周期。即-O0或-O1。分发代码时,使用-O2或-O3。-O3比-O2更优化,但编译时间明显更长。其他优化将在以下部分讨论。除了-Ox选项或级别对所有优化项目都是通用的,js优化、llvm优化、llvm链接时优化由它们自己独立的编译选项控制。注意:emccoptimizationflags的含义与gcc、clang或其他编译器的类似命名选项不同,因为优化JS代码与优化native代码有很大不同。参考文章中写了emcc优化级别和llvmbitcode优化级别的映射关系。2.高级编译器设置你可以给编译器传递一些标志来影响代码的生成,这也会影响性能。比如DISABLE_EXCEPTION_CATCHING,这些都可以在settings中看到。一些有用的标志是:NO_EXIT_RUNTIME:编译时使用-sNO_EXIT_RUNTIME=1,这样编译器就知道您不希望程序在运行main()函数后结束。这时候编译器会放弃对atexit和全局析构函数这两个函数的调用(如果不设置-sNO_EXIT_RUNTIME=1就会调用),减少了代码量,加快了启动速度。当您的main()函数完成但您仍想执行代码时,这很有用,例如在应用程序中使用主循环函数。注意:如果你的C代码中有emscripten_set_main_loop(),它会被emscripten检测到,emscripten不会在主调用结束后关闭运行时,但你最好在编译选项中添加-sNO_EXIT_RUNTIME=1,毕竟,这样可以减少不必要的代码生成,对吧?!3.代码大小本节描述与代码大小相关的优化和问题。它们非常适合大大小小的项目。在小型项目或库中,您获得的封装最少;在大型项目中,代码的绝对大小可能会导致您试图避免的问题(如启动速度慢)。3.1内存初始化默认情况下,emscripten会在.js文件中写入静态内存初始化代码。这会导致js文件很大,并且还会减慢启动速度。由于js引擎对数组大小的限制,这也可能导致js引擎抛出两个错误:arrayinitializertoolarge和Toomuchrecursion。emcc--memory-init-file1的设置会将这部分静态内存代码初始化放到.mem后缀的文件中,与.js文件分开。这个.mem文件将在调用main()函数和执行代码之前异步加载。注意:从Emscripten1.21.1开始,优化级别-O2及以上默认启用此设置。3.2代码大小和性能之间的平衡你可能想编译你的项目中性能不是很好的源文件,所以使用-Os和-Oz。其他部分使用-O2。(-Os和-Oz使用性能来减少代码的代码大小,它们类似于-O2)。注意:-oz编译比较耗时。3.3其他关于代码大小的提示(杂项)除了3.1和3.2,以下建议也可以减少代码大小:这篇文章很有帮助编译过程从bitcode到js使用llvm-lto,用法:--llvm-lto1。不允许嵌入/内联代码:-sINLINING_LIMIT=1。下面还有6条,就不一一翻译了。4.大型代码库的优化上面第三部分介绍的代码缩减部分对于大型代码库很有用。此外,还有一些其他主题对大型代码库优化很有用。4.1分离asm.js文件,避免内存高峰默认情况下,Emscripten会编译一个包含整个代码库的JS文件:一个是asm.js代码,一个是运行环境和连接的胶水代码浏览器。对于一个非常大的代码库,这在内存使用方面可能是低效的,因为一个脚本中的所有代码意味着js引擎可能会使用一些内存来解析和编译asm,并且在开始运行代码库之前,这部分内存可能不会被释放。在大型游戏中,开始运行代码可能需要动态分配一个大类型数组作为内存,因此您可能会看到一个内存“尖峰”,之后是临时编译内存(解析和编译asm部分内存将被释放)).如果足够大,这个峰值可能会导致浏览器内存不足并无法加载应用程序。这是Chrome的一个已知问题(其他浏览器似乎没有这个问题)。一种方法是分离出asm。js到另一个文件,并保证浏览器在编译asm和保证编译asm和运行程序之间有一个事件循环。这可以通过运行emcc--separate-asm来实现。4.2自运行4.3概述JavaScript引擎通常会缓慢地编译非常大的函数(相对于它们的大小),并且不会(或根本不会)有效地优化它们。解决这个问题的一种方法是使用“列轮廓”:将它们分解成更小的函数,以便更有效地编译和优化。“Columnoutlines”增加了整体代码大小并且可以使一些代码不那么优化。不过,大纲有时可以提高启动速度和运行速度。了解更多信息。OUTLINING_LIMIT选项定义了emscripten是否将一个函数分解为许多更小函数的值。如何选择合适的尺寸值进行反汇编,如何决定反汇编哪些函数,请点击这里。4.4变量的积极消除积极的变量消除试图删除尽可能多的变量,即使以重复表达式(增加代码大小)为代价。如果您有非常大的功能,这可以提高速度。例如,它可以使sqlite(它有一个包含数千行的巨大解释器循环)快7%。5.其他优化问题5.1C++异常在-o1(及以上)中默认被禁用。这可以防止生成try-catch块,它使代码运行得更快,也使代码更小。要在优化代码中重新启用异常,请运行emcc命令-sDISABLE_EXCEPTION_CATCHING=05.2内存增长使用-sALLOW_MEMORY_GROWTH=1命令的编译允许根据应用程序的需要更改内存总量。这对于事先不知道需要多少内存的应用程序很有用,但它会禁用一些优化。(目前正在改进。)5.3内联/嵌套内联通常会产生更大的函数,因为这些允许编译器的优化更有效。不幸的是,大函数比多个小函数运行得更慢,因为JavaScript引擎通常不会优化大函数(因为担心长JIT),或者优化它们会导致明显的停顿。注意:默认情况下-o1和-o2内联函数。具有讽刺意味的是,在某些情况下,这实际上会降低性能!5.4查看“代码优化通过”记录启用调试模式(EMCC_DEBUG)将每个通过的JavaScript优化输出到一个文件。6.不安全的优化你可能想尝试一些不安全的优化:-sFORCE_ALIGNED_MEMORY=1:使所有内存访问完全对齐。这会破坏实际上不需要内存对齐的代码。--llvm-lto1:可以优化llvm链接时间,在某些情况下很有用。但它们有一些已知问题,因此必须对代码进行全面测试。点击查看更多。--closure1:可以减少胶水代码的大小,减少启动时间。但是如果你没有做正确的闭包编译器,可能会有问题。7.性能分析现代浏览器具有JavaScript分析器,可以帮助找到代码中运行缓慢的部分。由于每个浏览器的分析器都有局限性,因此建议在多个浏览器中进行分析。为了保证编译后的代码包含足够的信息供分析,可以使用如下flags设置编译命令:emcc-O2--profilingfile.cpp8,用来排查性能不佳的emscrip10编译后的代码目前可以达到一半左右本地构建速度。如果性能明显低于预期,您可以在下面运行其他故障排除步骤:构建项目是一个两阶段过程:将源代码文件编译成LLVM,并从LLVM生成JavaScript。您是否在这两个步骤中使用相同的优化值(-o2或-o3)?在多个浏览器上测试。如果在一个浏览器上表现尚可,但在另一个浏览器上表现明显较差,那么您可以向我们提交错误报告,并注意将有问题的浏览器等相关信息写清楚。检查代码在Firefox中是否校验正确(查看Firefox控制台是否有:“successfullycompiledasm.jscode”信息输出)。如果您在使用最新版本的Firefox和Emscripten时看到验证错误,请提交错误报告。Emscripten代码移植系列文章Emscripten代码移植主题系列是emscripten中文站点的一部分。第一篇介绍代码的可移植性和局限性第二篇介绍Emscripten的运行环境第三篇第一篇介绍连接C++和JavaScript第三篇第二篇介绍embind第四篇介绍文件和文件系统第六篇介绍Emscripten如何调试代码
