-
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.
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. Note that -O3 optimization will generate faster code, but significantly bigger - which can be an issue on the web.
Important: Note that emmaken.py will discard optimization parameters. emmaken's job is just to generate .bc. You can then run llvm-opt on the bitcode file yourself, or pass --optimize to emscripten.py which will run LLVM optimizations. (We should rename that flag...)
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).
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.)
The following settings are very important for fast code:
-
MICRO_OPTS: This will use native JavaScript variables and other enhancements. -
RELOOP: This will generate native JavaScript code flow structures (ifs, loops, etc.), instead of emulating code flow using the switch-in-a-loop pattern. Note that currently using this setting will make the compiler very slow.
Other settings:
-
USE_TYPED_ARRAYS: 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. -
FAST_MEMORY: This is the amount of memory that is set to 0 at startup. This tells the JS engine that HEAP should be implemented as a flat fast array, otherwise it may implement HEAP as a hashtable which is extremely slow. This appears to be a problem mainly with V8. Try increasing the value ofFAST_MEMORYif you suspect this is the problem. You can also tryUSE_TYPED_ARRAYSsince they are always flat arrays, however you must then also make sure thatTOTAL_MEMORYis correct.
Advanced settings:
-
QUANTUM_SIZEof 1 can speed up your code, but is dangerous. See Memory Compression. The default is 4, which is the 'normal', safe value.
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).