Beyond Vectorization: DolphinDB's JIT Compiler Gets a Ground-Up Rebuild

DolphinDB
2026-07-24

DolphinDB has rebuilt its JIT compiler from the ground up, moving to MLIR (Multi-Level Intermediate Representation) as the underlying compilation framework. MLIR is a general-purpose framework for building compilers — it bridges high-level code and low-level machine code, giving DolphinDB a much more flexible foundation for optimization.

But the more interesting question isn't what's new under the hood. It's this: DolphinDB already has strong vectorized computing. So why invest in a JIT compiler at all?

Where Vectorization Hits Its Limits

"If you can vectorize it, don't write a loop" is close to gospel in data computing — true for NumPy, Pandas, SQL, and DolphinDB alike. Vectorization pushes element-wise work down into optimized batch operations, sidestepping the overhead of an interpreter grinding through a loop one element at a time.

But a growing share of real business logic doesn't fit that mold. Backtesting has to update position state trade by trade. Risk systems track account state and fire rules conditionally. Complex event processing updates state continuously as events arrive. Pricing models converge through iteration.

The common thread: each step depends on the last. It's not the same operation applied independently across a batch — it's a chain. Force that into vectorized form and you often get more complexity, not more speed.

Why Loops Are Slow — It's Not the CPU

The usual assumption is that loops are just slow to execute. In practice, in an interpreted language, the cost isn't the loop itself — it's the interpretation. Every iteration re-resolves variables, re-checks types, re-dispatches functions. Multiply that by a few million iterations and the interpreter overhead can dwarf the actual computation.

The traditional fix is to drop into C++ for the hot path — which works, but drags in a second language, a build pipeline, and a growing maintenance tax.

JIT compilation is the alternative: keep the ergonomics of scripting, get close to native speed.

What JIT Actually Changes

JIT doesn't change your algorithm — it changes how your code runs.

Normally, a script executes line by line through an interpreter. A JIT compiler watches for hot code paths, compiles them to native machine code the first time they run, caches the result, and runs the compiled version directly from then on. Same logic, no repeated interpretation. It's the same idea behind Java's HotSpot, the .NET CLR, V8, and Julia — different implementations, same principle: for loops, branches, and state-heavy code, compiling once and running natively beats re-interpreting every time.

Why Rebuild on MLIR

DolphinDB's earlier JIT had limited headroom for optimizing complex control flow and multi-layered type inference. MLIR gives it a richer intermediate representation to work with, which translates into better type inference, better control-flow optimization, and a foundation that can grow with future syntax and type support.

To be clear: JIT isn't a replacement for vectorization. For aggregation, filtering, and windowed computation, vectorization is still faster to write and faster to run — nothing here changes that. What JIT adds is coverage for the logic vectorization was never going to reach: the loops, branches, and stateful computation that make up a meaningful slice of real trading and risk systems.

Think of it as a division of labor — vectorization handles data parallelism, JIT handles control flow.

Using it is close to effortless: add an @jit annotation above a function, and DolphinDB compiles the hot path for you.

@jit
def positiveSum(v) {
    total = 0.0
    for (x in v) {
        if (x > 0) {
            total += x
        }
    }
    return total
}

(This particular example — a filter-and-sum — is simple enough that vectorization is still the better choice in practice. It's here purely to show the syntax.)

Developers can hand hot functions over to the new JIT compiler with essentially no changes to their existing code.

Where JIT Actually Pays Off

Not all code benefits from JIT. If a computation can already be done with a single sum() call, vectorization is still almost always the better choice. JIT earns its keep on hot-path logic where interpretation cost far outweighs the actual computation — for example:

  • Loops with heavy for/while usage
  • Business rules with complex if/else branching
  • Logic that repeatedly updates object state or dictionary contents
  • Path-dependent computation, iterative numerical solving
  • Real-time CEP event processing, state updates in high-frequency strategy backtesting

What these all have in common: they're hard to rewrite as vectorized expressions, but get executed thousands or millions of times.

Below are two concrete examples. Testing was done on Ubuntu 20.04 (kernel 5.15) with an i5-10400F CPU (6 cores / 12 threads).

Case 1: Computing Implied Volatility

Implied volatility generally has no closed-form solution — it has to be approximated iteratively, for example using bisection to progressively narrow a search range until the error falls within tolerance. Each step depends on the range from the previous step, so this path-dependent logic is hard to express in vectorized form and essentially has to be written as a loop.

@jit
def impliedVolatility(futurePrice, strikePrice, ttm, riskRate, carryRate, optionPrice, isCall) {
    high = 5.0
    low = 0.0
    do {
        mid = (high + low) / 2.0
        if (blackScholes(futurePrice, strikePrice, ttm, riskRate, carryRate, mid, isCall) > optionPrice) {
            high = mid
        } else {
            low = mid
        }
    } while ((high - low) > 0.00001)
    return (high + low) / 2.0
}

Every iteration recomputes an option price via blackScholes and narrows the range accordingly until convergence. Testing against 10,000 option contracts, repeated 10 times (results verified correct):

Case 2: Computing a Stop-Loss Index

Stop-loss logic is another classic path-dependent computation: current return has to be continuously compared against the historical peak, and the moment drawdown exceeds a threshold, the function should return immediately without scanning the rest of the data. This "compute-as-you-go, exit early when possible" pattern is likewise difficult to express with vectorization.

@jit
def stopLossIndex(ret, threshold) {
    currentReturn = 1.0
    peakReturn = 1.0
    i = 0
    while (i < size(ret)) {
        currentReturn *= 1.0 + ret[i]
        if (currentReturn > peakReturn) {
            peakReturn = currentReturn
        }
        drawdown = 1.0 - currentReturn / peakReturn
        if (drawdown >= threshold) {
            return i
        }
        i += 1
    }
    return -1
}

Tested against 1 million return records, repeated 10 times, with a stop-loss threshold of 0.15 (triggering at record 999,999; results verified correct):

Where JIT Doesn't Help

Beyond the two cases above, even a basic loop-based summation test showed over a 55x speedup with JIT (after the initial compilation warmup) compared to plain interpreted execution.

This doesn't mean every piece of code will see gains of this magnitude. The actual benefit of JIT depends heavily on code structure, data scale, and how stable the parameter types are. The right approach is to choose an implementation strategy based on the specific workload — not to treat JIT as the default choice for everything.

There are a few categories where JIT typically isn't a good fit:

  • Anything vectorization already handles well. A simple sum() or filter doesn't need JIT — you'll just pay compilation overhead for nothing.
  • Cold code that runs once. JIT's value comes from amortizing compilation cost over many runs. Run it once, and compiling is pure overhead.
  • Hot functions with unstable parameter types. JIT caches compiled code per type signature. If the types keep changing, you get repeated recompilation instead of reuse.

At its core, deciding whether to use JIT comes down to a simple trade-off: compilation cost + machine-code execution time < interpreted execution time. JIT only pays off when code runs often enough, and takes long enough, that this inequality clearly holds.

This refactor isn't about replacing vectorization — it's about closing the gap vectorization was never meant to cover. Vectorization handles data parallelism; JIT handles control flow. Together, they make DolphinDB's execution engine considerably more complete.