Skip to content

Expression Optimization

Muqsit Rayyan edited this page Nov 9, 2022 · 13 revisions

arithmexp performs several expression optimizations at the end of parsing stage to reduce computational wherever possible. To disable optimizations, replace Parser::createDefault() with Parser::createUnoptimized(). arithmexp executes the following optimization techniques on an expression:

  1. Constant folding — Constant sub-expressions are evaluated at the parsing stage rather than the evaluation stage. For example, the expression 2 * 3 * x gets rewritten as 6 * x.
  2. Idempotence Folding — Sub-expression containing multiple calls to idempotent functions is transformed to a sub-expression containing lesser function calls. For example, the expression round(round(x)) gets rewritten as round(x), and the expression min(min(x, y), z, min(w)) gets rewritten as min(x, y, z, w).
  3. Operator Strength Reduction — Computation is reduced by incorporating various mathematical identities to solve sub-expressions at the parsing stage. For example, the expression (x * y) ** 1 gets rewritten as x * y, while the expression (x * 2) / (4 * x) gets rewritten as a constant 0.5.
  4. Expression Reordering — Operators in commutative operations (addition and multiplication) are rearranged by shifting numeric literals towards the right and variables towards the left. This optimization helps other optimization techniques further optimize an expression. For example, the expression 2 * x * 4 gets rewritten as x * 2 * 4, and later to x * 6 during constant folding.

Aside from the gain in performance, optimizations can help in detecting errors early. For example:

  • the expression x / (y - y) fails during the parsing stage (rather than the evaluation stage) due to division by zero
  • the expression x % (y ** 0 - z ** 0) fails during the parsing stage due to modulo by zero

As the optimizer may rearrange the expression, the resulting expression may return a more or less precise value than the original (albeit being mathematically equivalent). For example, the parser may rearrange (39 * 126) / 47 as (39 / 47) * 126, resulting in float(104.5531914893617) instead of float(104.55319148936171) (an error of 1e-14) due to how floating-point operations are handled by programming languages.

Due to rearranging, arithmexp may appear more or less powerful than PHP itself in few cases. For example, the expression 2 ** 1025 / 2 ** 1024 evaluates to int(2), instead of float(NAN), because arithmexp evaluates the expression as 2 ** (1025 - 1024) (due to strength reduction optimization) instead of (2 ** 1025) / (2 ** 1024) as PHP does. Ofcourse, this rearrangement is only possible when the base value in the two exponentiation operations are known during parsing stage (i.e., the bases are constant). The expression x ** a / x ** b is rearranged as x ** (a - b) whereas the expression x ** a / y ** b is not.

Expressions are preprocessed in other ways as well that may be considered optimization. min() and max() for example are macros (not functions, although a function call is indifferentiable from a macro call in arithmexp in writing). Passing only one argument to either of the macros (such as min(mt_rand(1, 2)) gets resolved to the argument itself mt_rand(1, 2) (this can be verified by printing an expression containing min(mt_rand(1, 2)) versus min(mt_rand(1, 2), mt_rand(1, 2)), or testing it out on the demo site). pi() is also a macro and resolves into a numeric literal (instead of a function call). Similar preprocessing also occurs during constant substitution. Constants are resolved during parsing to reduce the lookup overhead of performing constant substitution during evaluation. These preprocessing methods are baked in arithmexp and occur even without optimizations (i.e., Parser::createUnoptimized()).

1. Quick Start

1.1. Installation

2. Documentation Notes

2.1. Constant
2.2. Function
2.3. Macro
2.4. Operator
2.5. Variable
2.6. Expression Optimization
2.7. Implementation Internals

Clone this wiki locally