» Optimizing Code
Generally you should first compile and run your code without optimizations (the default). Once you are sure that the code runs correctly, you can use the techniques in this article to make it load and run faster.
For example, to compile with optimization level
emcc -O2 file.cpp
The higher optimization levels introduce progressively more aggressive optimization, resulting in improved performance and code size at the cost of increased compilation time. The levels can also highlight different issues related to undefined behavior in code.
The optimization level you should use depends mostly on the current stage of development:
-O2to get a well-optimized build.
-Oscan produce an ever better build than
-O2, and are worth considering for release builds.
-O3builds are even more optimized than
-O2, but at the cost of significantly longer compilation time and potentially larger code size.
-Osis similar in increasing compile times, but focuses on reducing code size while doing additional optimization. It’s worth trying these different optimization options to see what works best for your application.
-O1, -O2etc.) are similar to gcc, clang, and other compilers, but also different because optimizing WebAssembly includes some additional types of optimizations. The mapping of the emcc levels to the LLVM bitcode optimization levels is documented in the reference.
Compiling source files to object files works as you’d expect in a native build system that uses clang and LLVM. When linking object files to the final executable, Emscripten does additional optimizations as well depending on the optimization level:
noinlinehave been lost at this point.)
There are several flags you can pass to the compiler to affect code generation, which will also affect performance — for example DISABLE_EXCEPTION_CATCHING. These are documented in src/settings.js. Some of these will be directly affected by the optimization settings (you can find out which ones by searching for
apply_opt_level in tools/shared.py).
Emscripten will emit WebAssembly by default. You can switch that off with
This section describes optimisations and issues that are relevant to code size. They are useful both for small projects or libraries where you want the smallest footprint you can achieve, and in large projects where the sheer size may cause issues (like slow startup speed) that you want to avoid.
You may wish to build the less performance-sensitive source files in your project using -Os or -Oz and the remainder using -O2 (-Os and -Oz are similar to -O2, but reduce code size at the expense of performance. -Oz reduces code size more than -Os.)
Separately, you can do the final link/build command with
-Oz to make the compiler focus more on code size when generating WebAssembly module.
In addition to the above, the following tips can help to reduce code size:
--pre-js, for example) then you need to make sure it uses closure annotations properly.
The following compiler settings can help (see
src/settings.js for more details):
-s INLINING_LIMIT=1. Compiling with -Os or -Oz generally avoids inlining too. (Inlining can make code faster, though, so use this carefully.)
-s FILESYSTEM=0option to disable bundling of filesystem support code (the compiler should optimize it out if not used, but may not always succeed). This can be useful if you are building a pure computational library, for example.
ENVIRONMENTflag lets you specify that the output will only run on the web, or only run in node.js, etc. This prevents the compiler from emitting code to support all possible runtime environments, saving ~2KB.
Link Time Optimization (LTO) lets the compiler do more optimizations, as it can
inline across separate compilation units, and even with system libraries.
LTO is enabled by compiling objects files with
-flto. The effect of this
flag is to emit LTO object files (technically this means emitting bitcode). The
linker can handle a mix wasm object files and LTO object files. Passing
-flto at link time will also trigger LTO system libraries to be used.
Thus, to allow maximal LTO opportunities with the LLVM wasm backend, build all
source files with
-flto and also link with
The previous section on reducing code size can be helpful on very large codebases. In addition, here are some other topics that might be useful.
If you hit memory limits in browsers, it can help to run your project by itself, as opposed to inside a web page containing other content. If you open a new web page (as a new tab, or a new window) that contains just your project, then you have the best chance at avoiding memory fragmentation issues.
Catching C++ exceptions (specifically, emitting catch blocks) is turned off by default in
-O1 (and above). Due to how WebAssembly currently implement exceptions, this makes the code much smaller and faster (eventually, wasm should gain native support for exceptions, and not have this issue).
To re-enable exceptions in optimized code, run emcc with
-s DISABLE_EXCEPTION_CATCHING=0 (see src/settings.js).
When exception catching is disabled, a thrown exception terminates the application. In other words, an exception is still thrown, but it isn’t caught.
Even with catch blocks not being emitted, there is some code size overhead unless you build your source files with
-fno-exceptions, which will omit all exceptions support code (for example, it will avoid creating proper C++ exception objects in errors in std::vector, and just abort the application if they occur)
C++ run-time type info support (dynamic casts, etc.) adds overhead that is sometimes not needed. For example, in Box2D neither rtti nor exceptions are needed, and if you build the source files with
-fno-rtti -fno-exceptions then it shrinks the output by 15% (!).
-s ALLOW_MEMORY_GROWTH=1 allows the total amount of memory used to change depending on the demands of the application. This is useful for apps that don’t know ahead of time how much they will need.
A few UNSAFE optimizations you might want to try are:
--closure 1: This can help with reducing the size of the non-generated (support/glue) JS code, and with startup. However it can break if you do not do proper Closure Compiler annotations and exports. But it’s worth it!
To ensure that compiled code contains enough information for profiling, build your project with profiling as well as optimization and other flags:
emcc -O2 --profiling file.cpp
Emscripten-compiled code can currently achieve approximately half the speed of a native build. If the performance is significantly poorer than expected, you can also run through the additional troubleshooting steps below: