JavaScript ahead of the reference runtime on six of eight speed tests
A JavaScript speed release. Floati measures its script engine against the reference JavaScript runtime on eight standard workloads: a numeric loop, recursion with a counter, a bitwise hash, reading object properties, building a string, building and dropping a tree of small objects, a Map with string keys, and array callbacks. In 0.1.382 Floati was behind on all eight, on four of them by 15 to 35 times. Now it is ahead on six, by 10 to 42 percent. The changes are all in Floati's own compilers: hash loops keep their numbers as 32-bit machine integers, multiplications and recursion counters stay exact integers, strings are built in place, Map keys carry their hash, array callbacks run without making a function object, and a loop with a try block now runs compiled. Every change has a switch that turns it off, a set of edge cases checked against the reference runtime on every compiler tier, and a deliberately broken version of the change that those checks must catch. Script time on 11 real pages is unchanged, and small objects are 24 bytes lighter. Two workloads are still behind, and they share one cause: how objects are stored and freed. Floati frees an object the moment nothing uses it, which keeps memory low and pauses short, but a page that builds and drops thousands of small objects pays for each one, where a collector that only looks at survivors pays almost nothing. We built and measured a young-object area for that this week; it was slower, so it is not in this release. The next step is a compact object layout and a way for short-lived objects to die without that per-object cost.
Six of eight workloads now faster than the reference runtime
Each workload runs a small function many times and keeps the best of five timings; the chart shows the middle of three such runs for Floati 0.1.382, this release and the reference JavaScript runtime, all on the same Mac. Before this release Floati was behind on every row. Recursion with a counter went from 69.7 ms to 1.6 ms because the recursive compiler now adds a counter once after the run instead of on every call; the bitwise hash went from 71 ms to 3.9 ms because 32-bit hash arithmetic now stays in 32-bit registers, with a multiply-and-add split so the two halves run side by side; string building went from 47 ms to 1 ms because character codes are appended in place.
What is still behind, and why
Reading properties of 2,000 objects takes 4.0 ms against the reference runtime's 2.6, and building and dropping a tree of small objects 3.9 ms against 0.9. We measured where the time goes before changing anything. For object properties it is not the checks or the arithmetic: a hand-written copy of the compiled loop ran at the reference runtime's speed once each object was as small as the reference runtime's, so this release makes every small object 24 bytes smaller (a three-field object went from 112 to 88 bytes). For the tree, two thirds of the time is freeing the objects one by one. We built a young-object area where dead objects are found together in one sweep, and measured it: it was slower (4.8 to 7.3 ms), because under reference counting every dead object must still let go of what it points at. So it stays out, and the plan for the next releases is written down with these measurements.