Does Mathematica reuse previous computations?

Here I implemented three different versions of Gaussian filtering (for periodic data). It took me a while to adjust the constants and still some of them might be wrong.

Prepare the Gaussian kernel

n = 200000;
σ = .1;
t = Subdivide[-1. Pi, 1. Pi, n - 1];
ker = 1/Sqrt[2 Pi]/ σ Exp[-(t/σ)^2/2];
ker = Join[ker[[Quotient[n,2] + 1 ;;]], ker[[;; Quotient[n,2]]]];

Generate noisy function

u = Sin[t] + Cos[2 t] + 1.5 Cos[3 t] + .5 RandomReal[{-1, 1}, Length[t]];

The three methods with their timings. As Niki Estner pointed out, GaussianFilter with the option Method -> "Gaussian" performs much batter than GaussianFilter with the default emthod.

kerhat = 2 Pi/Sqrt[N@n] Fourier[ker];
vConvolve = (2. Pi/n) ListConvolve[ker, u, {-1, -1}]; // RepeatedTiming // First
vFFT = Re[Fourier[InverseFourier[u] kerhat]]; // RepeatedTiming // First
vFilter = GaussianFilter[u, 1./(Pi) σ n, Padding -> "Periodic"]; // RepeatedTiming // First
vGaussian = GaussianFilter[u, 1./(Pi) σ n, Padding -> "Periodic", Method -> "Gaussian"]; // RepeatedTiming // First

0.0038

0.0058

0.055

0.0072

ListLinePlot[{u, vFFT, vFilter, vConvolve}]

enter image description here

From further experiments with different values for n, GaussianFilter seems to be slower by a factor 10-20 over a wide range of n (from n = 1000 to n = 1000000). So it seems that it does use some FFT-based method (because it has the same speed asymptotics) but maybe some crucial part of the algorithm is not compiled (the factor 10 is somewhat an indicator for that) or does not use the fastest FFT implementation possible. A bit weird.

So, to my own surprise, your idea of computing the kernel once does help but for quite unexpected reasons.


It's hard to know for sure, but one way to test for caching is to apply a single command to lots of data sets, or to apply the command separately to each set. For instance:

n = 5000;
data = RandomReal[{-1, 1}, {n, 10000}];
GaussianFilter[#, 100] & /@ data; // AbsoluteTiming
Do[GaussianFilter[data[[i]], 100], {i, n}] // AbsoluteTiming
Do[GaussianFilter[data[[i]], 100 + RandomInteger[{-15, 15}]], {i, n}] // AbsoluteTiming

The second line generates 5000 different sets of data, each 10000 length. The third applies one Gaussian filter to all the data sets. The third line applies a separate GaussianFilter to each set. The final line forces the GaussianFilter to recompute its kernel. The timings are pretty much the same. This suggests that whatever is happening, the time needed to calculate the Gaussian filter parameters is pretty negligeable.