To work, Inline caching requires both the techniques we’ve just looked at: type inference and hidden classes. When the compiler encounters a new object it caches its hidden class along with any inferred datatypes. If that structure is encountered again anywhere else in the code then it can be quickly compared to the cached version. If a match is found then the previously generated stub of optimised machine code can be reused. If the structure or type has changed then it may fall back to slower generic code, or in some modern compilers can even perform polymorphic inline caching – i.e generate additional stubs on the same structure, but for each datatype.
Once the compiler has understood the structure of your code and the datatypes within it, it becomes possible to perform a whole range of other optimisations. Here are just a few examples:
inline expansion. aka “inlining”
Function calls are computationally expensive because they require some kind of lookup, and lookups can be slow. The idea behind inline expansion is to take the body of the function being called and drop that code into where it was called. This avoids branching and produces faster code, but at the expense of additional memory.
loop-invariant code motion. aka “hoisting”
Loops are a prime candidate for optimisation. Moving unnecessary computation out of a loop can greatly improve performance. The most common example of this would be a for-loop iterating over the contents of an Array. It is computationally redundant to calculate the length of the Array every time you loop through it. The length can be pre-computed into a variable and “hoisted” out of the loop.
This is the practice of pre-computing the values of constants or variables that do not change throughout the lifetime of the program.
Similar to constant folding, this optimisation works by scanning your code for expressions that evaluate to the same value. Those expressions can then be replaced with a variable holding the pre-computed value.
Dead code is defined as unused or unreachable code. If there is a function in your program that is never called then there’s absolutely no point in optimising it. In fact it can be safely eliminated, which also reduces the overall size of the program.