Reference
The parser compiles a formula to byte-code once and interprets it after that.
The accelerator goes one step further: it turns the byte-code into x86-64
machine code and calls it directly. The API does not change - you construct
TJitParser instead of TMathParser and carry on.
The rule it never breaks: anything it cannot compile falls to the stage below it - the intermediate one, and the interpreter behind that. That fall back is not a setting: there is no way to switch it off, because fast but wrong is not a trade this library makes.
| Kind | Covered |
|---|---|
| Arithmetic | + - × ÷, signs, nested brackets, constants |
| Comparison | all six, with the epsilon the parser is configured with |
| Branching | if, while, repeat - lazily, as the interpreter does |
| Variables | both kinds: bound by typed reference, and boxed in a TValue |
| Scope | get and set on script variables |
| Calls | sin cos tan sqrt sqr ln exp abs arctan |
Each of these hands the formula down a stage, whole: to the intermediate
one, and to the interpreter if that declines it too. The reason
is available as CodeReason if you want to know why a particular
formula did not compile.
| Declines | Because |
|---|---|
| A call it has no machine version of | anything outside the nine above |
| A function with parameters | mean, poly and the rest of the variadic set |
| A string constant | the code generator works in Double only |
| An integer beyond exact range | above 253 a Double stops counting by ones |
| A variable it cannot type | a type with no machine representation |
| A frame deeper than 4 KB | a formula with hundreds of live intermediates |
Every row compares the same work done twice, with the accelerator and
without it. What one row measures is not what the next one does, so it is
worth saying plainly: the first three are a single evaluation of a formula
that is already parsed - the interpreter walking the script against machine
code running it; the loop row is one iteration inside a script that loops ten
thousand times; the bulk rows are one input out of an array handed over in a
single call, against the same inputs fed one AsDouble at a
time.
Measured on Delphi 13, Windows, x86-64. The number of runs each row was averaged over is in the table, because it is not the same for all of them. The programs that produce these numbers ship with the repository - run them on your own machine rather than trusting the table.
| Formula | Interpreted | Compiled | Times faster | Runs |
|---|---|---|---|---|
| x * 2 + 1 | 142.2 | 11.6 | 12× | 2 000 000 |
| polynomial, degree 3 | 620.0 | 20.8 | 30× | 1 000 000 |
| heavy math chain | 967.0 | 106.5 | 9× | 500 000 |
| loop, 10 000 iterations | 3969.2 | 35.5 | 112× | 200 000 |
| bulk mode, x * 2 + 1 | 893.0 | 7.7 | 116× | 500 000 |
| bulk mode, polynomial | 1968.6 | 11.8 | 167× | 500 000 |
Bulk mode hands the accelerator an array of inputs and gets an array of answers, so the call overhead is paid once instead of a million times.
A generator writes random formulas - nested brackets, mixed precedence, branches, loops - and both engines evaluate each one. The run that ships with the repository covers 3 000 formulas with zero disagreements, and the compiler declined none of them. When it does decline something it says why in one word, and the stage below answers instead.
The interpreter keeps intermediate values in Extended; the
accelerator works in Double. On a build where Extended
is wider than 64 bits - 32-bit Delphi, and FPC on Linux - the last bit of a
long chain can differ between the two.
The fuzzer compares within an epsilon, so it passes; a bit-exact comparison would not. If your work needs the two engines to agree bit for bit, stay on the interpreter. See Limitations.
| Requirement | Detail |
|---|---|
| Processor | x86-64 only - there is no ARM or 32-bit code generator |
| Operating system | Windows and Linux; memory is taken with VirtualAlloc or mmap as appropriate |
| Memory protection | the page is writable while the code is emitted, then read and execute - never both at once, so W^X is respected |
| Fallback | on any other target the machine-code stage is skipped and the intermediate one answers, with the interpreter behind it - nothing breaks |