2026-10-04: Menai performance improvements

Published: 2026-10-04

A couple of Menai performance fixes today, plus a nice change to the VM instruction format that lets opcodes use constants directly.

LICM issue

After the letrec inliner change it turns out the LICM pass was missing some optimization opportunties that it used to catch. This led to a particular regression on the zip-create benchmark's many-128 test.

Fixing the LICM issue restored the performance.

Allow constants directly in VM opcodes

Something I've been thinking about for a while is enabling VM opcodes to use constants as well as registers.

Almost all opcodes used registers for source operands but a relatively straightforward tweak to the instruction format would let them also use constants from the constant pool directly. I stole 3 bits from the opcode field, so we can now only have 8k distinct opcodes instead of 64k, but those bits now indicate if src0, src1, or src2 are registers or constants.

Opcodes are now tagged as to whether they can accept constants and a peephole pass runs before the register slot allocator and attempts to move constant definitions into opcodes. If all uses go to zero then we can also eliminate the discrete constant load.

This took about 4 hours to get right (thank you DeepSeek)

A small number of benchmarks suffer minor performance regressions but most exhibit a 2% to 10% improvements. A couple of outliers are nearer 20%.

Latest performance summary

Here are the latest performance numbers - there are a few minor losses compared with the captured numbers from 2026-09-30, but

BMP-DECODE
────────────────────────────────────────────────────────
Case                            mean (ms)       min (ms)
────────────────────────────────────────────────────────
truecolour-64x64                    0.305          0.282
truecolour-128x128                  1.283          1.256
truecolour-topdown-128x128          1.285          1.241
truecolour-alpha-128x128            1.609          1.572
padded-65x64                        0.305          0.291
────────────────────────────────────────────────────────


BMP-ENCODE
────────────────────────────────────────────────────────
Case                            mean (ms)       min (ms)
────────────────────────────────────────────────────────
truecolour-64x64                    0.781          0.749
truecolour-128x128                  7.251          7.215
truecolour-topdown-128x128          7.316          7.284
truecolour-alpha-128x128           10.122          9.950
padded-65x64                        0.822          0.813
────────────────────────────────────────────────────────


CALENDAR
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
short_5d                          0.024          0.020
month_20d                         0.074          0.072
quarter_60d                       0.216          0.213
year_250d                         0.890          0.885
months_600d                       1.587          1.582
──────────────────────────────────────────────────────


DEFLATE-COMPRESS
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
text-4k                           1.504          1.474
text-32k                          9.324          9.243
incremental-16k                  13.394         13.229
runs-32k                         80.431         79.140
──────────────────────────────────────────────────────


DEFLATE-DECOMPRESS
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
text-4k                           0.383          0.372
text-32k                          0.649          0.636
incremental-16k                   1.021          1.009
stored-8k                         0.202          0.196
runs-32k                          2.165          2.125
──────────────────────────────────────────────────────


JSON-DECODE
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
object                            0.013          0.011
flat_array                        0.078          0.076
flat_object                       0.071          0.069
mixed_nested                      0.155          0.154
string_heavy                      0.056          0.054
numbers_array                     0.016          0.015
unicode_strings                   0.007          0.007
long_string                       0.062          0.059
deep_array                        0.162          0.158
──────────────────────────────────────────────────────


JSON-ENCODE
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
object                            0.012          0.010
flat_array                        0.013          0.012
flat_object                       0.029          0.028
mixed_nested                      0.058          0.057
string_heavy                      0.074          0.073
numbers_array                     0.009          0.008
unicode_strings                   0.003          0.003
long_string                       0.212          0.209
──────────────────────────────────────────────────────


PNG-DECODE
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
greyscale-64x64                   2.491          2.468
greyscale-alpha-64x64            13.647         13.599
palette-128x128                   5.912          5.825
truecolour-128x128               78.822         78.704
truecolour-alpha-128x128        105.677        105.240
truecolour-192x192              179.084        178.244
──────────────────────────────────────────────────────


PNG-ENCODE
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
truecolour-48x48                111.379        107.298
truecolour-96x96               1667.060       1655.026
truecolour-alpha-96x96         2946.559       2941.449
──────────────────────────────────────────────────────


RUBIKS_LIST
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
1-move                            0.063          0.060
2-move                            0.064          0.061
3-move                            0.143          0.138
4-move                            1.491          1.482
5-move                            5.043          5.040
6-move                           32.440         32.345
7-move                           71.965         71.804
──────────────────────────────────────────────────────


RUBIKS_VECTOR
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
1-move                            0.057          0.054
2-move                            0.057          0.054
3-move                            0.130          0.127
4-move                            1.354          1.348
5-move                            4.597          4.590
6-move                           29.613         29.569
7-move                           66.306         66.117
──────────────────────────────────────────────────────


SORT
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
n=10                              0.005          0.004
n=50                              0.033          0.030
n=100                             0.069          0.067
n=250                             0.202          0.200
n=500                             0.453          0.449
n=1000                            1.015          1.011
n=2500                            2.922          2.912
n=5000                            6.398          6.372
n=10000                          14.107         14.074
──────────────────────────────────────────────────────


SUDOKU_LIST
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
Easy (36 givens)                 14.751         14.741
Medium (30 givens)                0.217          0.212
Hard (25 givens)               3171.700       3171.700
Expert (23 givens)              180.422        180.422
──────────────────────────────────────────────────────


SUDOKU_VECTOR
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
Easy (36 givens)                 10.925         10.895
Medium (30 givens)                0.163          0.161
Hard (25 givens)               2416.305       2416.305
Expert (23 givens)              138.083        138.083
──────────────────────────────────────────────────────


ZIP-CREATE
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
single-deflate                    0.684          0.665
single-stored                     0.023          0.018
mixed-16                          4.662          4.632
many-128                         57.721         57.486
large-256k                       76.380         75.320
──────────────────────────────────────────────────────


ZIP-ENTRIES
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
single-deflate                    0.004          0.002
single-stored                     0.003          0.002
mixed-16                          0.015          0.013
many-128                          0.096          0.086
large-256k                        0.003          0.002
──────────────────────────────────────────────────────


ZIP-EXTRACT
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
single-deflate                    0.299          0.285
single-stored                     0.202          0.197
mixed-16                          0.879          0.864
many-128                          9.704          9.629
large-256k                        3.179          3.147
──────────────────────────────────────────────────────


ZLIB-COMPRESS
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
text-4k                           1.697          1.680
text-32k                         11.009         10.849
incremental-16k                  14.386         14.256
runs-32k                         86.333         85.400
──────────────────────────────────────────────────────


ZLIB-DECOMPRESS
──────────────────────────────────────────────────────
Case                          mean (ms)       min (ms)
──────────────────────────────────────────────────────
text-4k                           0.608          0.595
text-32k                          2.448          2.423
incremental-16k                   1.896          1.884
runs-32k                          3.963          3.942
──────────────────────────────────────────────────────