Compiling Sequential Numeric Kernels in Wolfram Language: An EWMA Example
An EWMA over a large vector is sequential and awkward to vectorize. This post shows how to isolate the hot loop with Compile, verify it against a reference, and handle toolchain and type pitfalls.
28 Jun 2026, 15:17 UTC

The sequential dependency problem
An exponentially weighted moving average (EWMA)—also called a leaky integrator—is a simple filter where each output value depends on the previous one. For a vector x and smoothing parameter alpha, the recurrence is s = alpha*x[i] + (1-alpha)*s. Because s carries forward, you cannot compute all outputs in parallel with ordinary vectorized arithmetic. FoldList expresses the recurrence cleanly, but for very large real vectors it is often not the fastest path in the Wolfram Language.
Compile the inner loop, not the whole workflow
The practical decision is to keep data loading, validation, and plotting in ordinary Wolfram Language code, and hand only the hot loop to Compile. A compiled function expects machine-number arithmetic and simple control flow—Do, Table, Part, and numeric built-ins—rather than symbolic rewriting or pattern matching. The interface should be plain numeric lists, for example {{x,_Real,1},{alpha,_Real}}, so the compiler can generate tight machine code.
Worked example: an EWMA kernel
Run the following in a Wolfram Language notebook or script kernel. No special permissions are required, but a supported C compiler must be installed on the system for CompilationTarget -> "C" to succeed.
ewma = Compile[{{x, _Real, 1}, {alpha, _Real}},
Module[{n = Length[x], y, s = 0.},
y = Table[0., {n}];
Do[
s = alpha*x[[i]] + (1. - alpha)*s;
y[[i]] = s,
{i, n}
];
y
],
CompilationTarget -> "C"
];
Before benchmarking, verify correctness against a simple reference. A pure Wolfram Language implementation using FoldList is easy to read and serves as ground truth.
reference = Function[{x, alpha},
FoldList[alpha*#2 + (1. - alpha)*#1 &, 0., x]
];
(* Small input check *)
data = {1.0, 2.0, 3.0, 4.0, 5.0};
alpha = 0.2;
compiledResult = ewma[data, alpha];
referenceResult = reference[data, alpha];
match = Chop[compiledResult - referenceResult];
The expected check is that match contains only values at or near zero, or equivalently that SameQ[Chop[compiledResult - referenceResult], {0., 0., 0., 0., 0.}] evaluates to True. Also test edge cases: an empty list {} and a single-element list {1.0}. If the C toolchain is missing, remove CompilationTarget -> "C" and rerun; the function will fall back to the Wolfram Virtual Machine, which changes performance characteristics but not the correctness check.
Diagnostic decision: packed, unpacked, and integer inputs
Compiled code is stricter than top-level evaluation. A packed array of machine reals is ideal. An unpacked list or an integer-valued list may trigger coercion, evaluation outside the compiled kernel, or a compile-time message. Inspect behavior explicitly:
packed = Developer`ToPackedArray[{1.0, 2.0, 3.0}];
unpacked = {1.0, {2.0}, 3.0}; (* ragged, not a rank-1 real tensor *)
integers = {1, 2, 3};
Calling ewma[packed, 0.2] should stay inside the compiled path. Calling ewma[unpacked, 0.2] is likely to fail or evaluate outside the kernel because the type does not match {_Real, 1}. Calling ewma[integers, 0.2] may coerce or may produce a compile-time warning depending on the installed version. Treat these as diagnostic probes, not production inputs.
Trade-offs and limitations
Compiled functions are less debuggable than ordinary Wolfram Language code. A non-compilable construct can trigger evaluation outside the compiled kernel or a compile-time error, and type changes—Integer versus Real, or packed versus ragged lists—are common sources of surprise. Compiled code can also bypass some high-level error checking, so validate inputs and handle empty lists or non-finite values before calling the kernel. Finally, compiler availability, optimization, and the set of supported compilable functions vary by version and platform; timings and fallback behavior are not portable.
Actionable rule
Profile first. Use AbsoluteTiming or RepeatedTiming on a packed real vector of realistic size, and report the median of several runs rather than a single timing. Compile the smallest hot loop that owns the runtime, keep its interface numeric and packed, and add a correctness test against a simple reference implementation before using it in production. If the environment lacks a supported C toolchain, test the same function without requesting C and accept the different performance profile.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.