Code optimization, as the name indicates can be explained as the method that is used for the modification of codes in order to improve the efficiency and quality of the code. As a result of optimization, a program may become lesser in size, consume lesser memory, execute more rapidly, or performs fewer operations (input/output). An optimized program should possess exactly the same outputs and side effects as that of its non-optimized program. However, the benefits are given more weight in comparison to alteration in program behavior.
Optimization can also be referred to as a program transformation technique, performed either by optimizers or programmers. An optimizer is a specialized software or an inbuilt unit of a compiler which is also called as an optimizing compiler. Modern processors are also used to optimize the order of execution of code instructions. Code optimization is generally performed at the end of the developmental stage as it reduces the readability and adds code in order to increase the performance.
2 Main Types of Code Optimization
1. High-level optimizations, intermediate level optimizations, and low-level optimizations
High-level optimization is a language dependent type of optimization that operates at a level in the close vicinity of the source code. High-level optimizations include inlining where a function call is replaced by the function body and partial evaluation which employs reordering of a loop, alignment of arrays, padding, layout, and elimination of tail recursion.
Most of the code optimizations performed fall under intermediate code optimization which is language independent. This includes:
- The elimination of common subexpressions – This type of compiler optimization probes for the instances of identical expressions by evaluating to the same value and researches whether it is valuable to replace them with a single variable which holds the computed value.
- Constant propagations – Here, expressions which can be evaluated at compile time are identified and replaced with their values.
- Jump threading – This involves an optimization of jump directly into a second one. The second condition is eliminated if it is an inverse or a subset of the first which can be done effortlessly in a single pass through the program. Acyclic chained jumps are followed till the compiler arrives at a fixed point.
- Loop invariant code motion – This is also known as hoisting or scalar promotion. A loop invariant contains expressions that can be taken outside the body of a loop without any impact on the semantics of the program. The above-mentioned movement is performed automatically by loop invariant code motion.
- Dead code elimination – Here, as the name indicates, the codes that do not affect the program results are eliminated. It has a lot of benefits including reduction of program size and running time. It also simplifies the program structure. Dead code elimination is also known as DCE, dead code removal, dead code stripping, or dead code strip.
- Strength reduction – This compiler optimization replaces expensive operations with equivalent and more efficient ones, but less expensive. For example, replace a multiplication within a loop with an addition.
Low-level optimization is highly specific to the type of architecture. This includes the following:
- Register allocation – Here, a big number of target program variables are assigned to a small number of CPU registers. This can happen over a local register allocation or a global register allocation or an inter-procedural register allocation.
- Instruction Scheduling – This is used to improve an instruction level parallelism that in turn improves the performance of machines with instruction pipelines. It will not change the meaning of the code but rearranges the order of instructions to avoid pipeline stalls. Semantically ambiguous operations are also avoided.
- Floating-point units utilization – Floating point units are designed specifically to carry out operations of floating point numbers like addition, subtraction, etc. The features of these units are utilized in low-level optimizations which are highly specific to the type of architecture.
- Branch prediction – Branch prediction techniques help to guess in which way a branch functions even though it is not known definitively which will be of great help for the betterment of results.
- Peephole and profile-based optimization – Peephole optimization technique is carried out over small code sections at a time to transform them by replacing with shorter or faster sets of instructions. This set is called as a peephole.
Profile-based optimization is performed on a compiler which has difficulty in the prediction of likely outcome of branches, sizes of arrays, or most frequently executed loops. They provide the missing information, enabling the compilers to decide when needed.
2. Machine-independent optimization and machine-dependent optimization
Machine-independent optimization phase tries to improve the intermediate code to obtain a better output. The optimized intermediate code does not involve any absolute memory locations or CPU registers.
Machine-dependent optimization is done after generation of the target code which is transformed according to target machine architecture. This involves CPU registers and may have absolute memory references.
To conclude, code optimization is certainly required as it provides a cleaner code base, higher consistency, faster sites, better readability and much more.