-
Notifications
You must be signed in to change notification settings - Fork 3
Optimizing Code
By default Emscripten will compile code in a fairly safe way, and without all the possible optimizations. You should generally try this first, to see if things work properly (if not, see the Debugging page). Afterwards, you may want to compile your code so it runs faster. This page gives some tips on how to do that.
Note: You can also optimize the source code you are compiling into JavaScript, see Optimizing the source code.
The recommended way to optimize code is with emcc. emcc works like gcc, for example
emcc file.cpp
will compile file.cpp to JavaScript, and
emcc file.bc
will compile bitcode to JavaScript. In both cases you can specify optimization, for example
emcc -O3 file.cpp
To see what the different optimizations levels do, run
emcc --help
Note that the meaning of -O1, -O2 etc. in emcc are not identical to gcc, even though they have been chosen to be as familiar as possible! They can't be, because optimizing JavaScript is very different than optimizing native code. See the --help as mentioned before for details.
The following is how you should normally optimize your code:
- First, run emcc on your code without optimization. The default settings are set to be safe. Check that your code works (if not, see the Debugging page) before continuing.
- Try to optimize with
-O3. This aggressively optimizes in various ways, including dangerous ones. If your code works, great! The only additional optimization worth investigating is memory compression, see the section below. - If not, you should try
-O2. That includes most optimizations, and should always work. If this fails, please file a bug. - If
-O2works, you can stop there, the code is fairly well optimized in most cases. However,-O3is significantly faster, so you can investigate what stops it from working. See the "Advanced Optimization Issues" sections below. You can probably use-O2with some of the optimizations from those sections, giving you performance in between-O2and-O3.
The rest of this page contains some more complex optimization methods.
CORRECT_SIGNS, CORRECT_OVERFLOWS and CORRECT_ROUNDINGS are needed in some code. They add a lot of runtime overhead though. If you can, disable them entirely (by compiling with -s OPTION=0 etc., or by editing src/settings.js). Test your code carefully with those options disabled, because it is very possible it will no longer run properly.
If you can't disable them entirely, you can enable corrections for specific lines. Setting the CORRECT_* option to 2 (see the linespecific test for more) will correct only those lines.
To automatically find which lines need correction, you can use Emscripten's Profile Guided Optimization (PGO), described in the next section.
To optimize your code with PGO, you should do the following steps:
- Compile your code with
PGO=1, CHECK_SIGNS=1, CHECK_OVERFLOWS=1. The generated code is now instrumented to correct everything, and to take note of what needed correction. - Run your code on a representative workload. The code will run slowly because of the PGO instrumentation. PGO info will be written out when the program stops normally (if it doesn't stop normally, you can call
CorrectionsMonitor.print()manually). Save the PGO output. - Recompile your code with something like
pgo_data = read_pgo_data(pgo_filename)
Settings.CORRECT_SIGNS = 2
Settings.CORRECT_SIGNS_LINES = pgo_data['signs_lines']
Settings.CORRECT_OVERFLOWS = 2
Settings.CORRECT_OVERFLOWS_LINES = pgo_data['overflows_lines']
Here read_pgo_data is a utility function from tools/shared.py, using that we take the processed PGO output and use corrections in mode 2 (correct only the specified lines) on the right lines.
Notes:
- You should compile your source code with -g to see the original source file and line numbers in the generated JavaScript.
- Your code must be run on a representative workload. Corrections will only be done if they were seen to be needed, so if you later run on different input that uses different code paths, things may break.
- There is no
CHECK_ROUNDINGS, so PGO can't work on roundings corrections. This is rarely needed and has much less runtime overhead though, so just check if your code only works withCHECK_ROUNDINGS, and if so, build that way. - For the first step, you might need
CHECK_SIGNED_OVERFLOWS=1in rare cases and not justCHECK_OVERFLOWS=1.
(Note for manual tweaking of corrections - you can probably ignore this - you don't necessarily need to recompile each time. You can edit the generated source. unSign, for example, takes as a third parameter whether to ignore problems, so changing that to true will ignore signing on that line.)
Typed arrays in JavaScript can be much faster than untyped arrays. However this does not always lead to faster code, so you should check if it does or not. There are also two different typed array modes, see Code Generation Modes.
QUANTUM_SIZE of 1 can speed up your code, but is dangerous, see Code Generation Modes. The default is 4, which is the 'normal', safe value.
Try changing I64_MODE and/or DOUBLE_MODE to 0. This may break code in some cases (see Code Generation Modes for details), but on most code it will work and be much faster.
In -O3, exception catching is disabled. This prevents the generation of try-catch blocks, which lets the code run much faster. If you can't use -O3, it is worth doing -s DISABLE_EXCEPTION_CATCHING=1 together with -O2.
The following are some additional topics related to optimization. You probably don't need them.
For additional speed, JavaScript optimizers can be run after Emscripten. The best is probably the Closure Compiler, which both minifies and optimizes the code. The YUI Compressor is also useful (tends to optimize less, but runs faster).
The closure compiler is run automatically by emcc with -O3.
You can optimize the .ll file that Emscripten receives by running optimizations in the compiler that generates the .ll file (clang or llvm-gcc). Or you can run llvm-opt on an LLVM bitcode file. However, these LLVM optimizations are neither safe nor portable, they are designed in mind for native code and not JavaScript. It is entirely possible they will break Emscripten. There is a safe subset of them, which are applied if you call emscripten.py with --optimize or if you tell emcc to optimize your code (any setting greater than 0 will use LLVM safe optimizations).
An additional optimization you can run before Emscripten is the dead function elimination tool, that is in tools/dead_function_eliminator.py in Emscripten. This tool will scrub an .ll file and remove all functions that cannot be run, leaving only those functions that can be reached (through some chain of calls) from main() or from a global constant. This is useful in reducing the size of the .ll file, which both leads to smaller code and faster compilation, but make sure it doesn't remove functions that you want left in (if you are compiling a library, for example).