Speeding up PGFPlots using LuaTeX

Posted on October 21, 2018

In the previous post, I compared the speed of Metapost and TikZ for drawing similar graphics and found that TikZ is three to five times slower than Metapost. Although TikZ is slower, it does provide a much higher level interface to drawing graphics and shifting to Metapost can take a lot of user time, which is often more valuable than computer time.

As an example, consider the PGFPlots package, which provides a high-level interface for drawing function plots. Although there is a Metapost package for drawing graphs, PGFPlots has more features and better documentation. In this post, I show that we can speed up plotting functions using PGFPlots by offloading the function computation to LuaTeX.

As an example, consider the plot of the following function

0.5 - 0.5*exp(-3*x)*(cos(5.196*x) + 0.5774*sin(5.196*x))

which is the step response of a second order dynamical system (the exact details are not relevant for this post). PGFPlots makes it very simple to plot such functions:

\usemodule[pgfplots]
\starttext
\starttikzpicture
    \startaxis
        [
          width=6cm,
          trig format plots=rad,
          xmin=0, xmax=4,
        ]

    \addplot[domain=0:4, samples=500]
        {0.5 - 0.5*exp(-3*x)*(cos(5.196*x) + 0.5774*sin(5.196*x)) };
    \stopaxis
\stoptikzpicture
\stoptext

On my laptop, the above code takes 1.341s (measured using the \testfeatureonce macro explained in the previous post). Most of the time is spent in calculating the algebric expression. We can speed this up by defining a Lua function that does the calculation.

\usemodule[pgfplots]
\starttext
\startluacode
  local exp = math.exp
  local cos = math.cos
  local sin = math.sin
  local step = function(x)
      return 0.5 - 0.5*exp(-3*x)*(cos(5.196*x) + 0.5774*sin(5.196*x))
  end

  thirddata = thirddata or {}
  thirddata.step = function(x)
      return context(step(x))
  end
\stopluacode

\starttikzpicture
    [declare function={
        step(\x) = \ctxlua{thirddata.step(\x)};
    }]

    \startaxis
        [
          use fpu=false,
          width=6cm,
          xmin=0, xmax=4,
        ]

    \addplot[domain=0:4, samples=500] {step(x)};
    \stopaxis
\stoptikzpicture
\stoptext

On my laptop, this takes 0.333s. So, plotting in PGFPlots is four times faster if we use Lua to do more complicated calculations.

I don’t know the internals of PGFPlot well, but recent versions of PGFPlot do use Lua for some of the calcuations. So, I don’t completely understand why I get such a big speedup by doing the calculations in Lua.

Addendum

Henri Menke pointed out in the comments that using fpu=false only works for simple plots and fails for plots with log scale. As explained in his excellent article on using LuaTeX FFI, a more robust solution is to convert back and forth between fpu float and lua float. The code below does that:

\usemodule[pgfplots]
\starttext
\startluacode
  local exp = math.exp
  local cos = math.cos
  local sin = math.sin

  local step = function(x)
      return 0.5 - 0.5*exp(-3*x)*(cos(5.196*x) + 0.5774*sin(5.196*x))
  end

  thirddata = thirddata or {}
  thirddata.step = function(x)
      return context(step(x))
  end
\stopluacode

\pgfmathdeclarefunction{step}{1}
    {\pgfmathfloatparsenumber
        {\ctxlua{thirddata.step(\pgfmathfloatvalueof{#1})}}}

\starttikzpicture

    \startaxis
        [
          width=6cm,
          xmin=0, xmax=4,
        ]

    \addplot[domain=0:4, samples=500] {step(x)};
    \stopaxis
\stoptikzpicture
\stoptext

In his article, Henri mentions in PGF version 3, one can use Lua functions to convert between fpu float and lua float, but that version is not available in current texlive or context standalone. Running the above code on my laptop takes 0.377s; slightly slower than the previous code but still in the ballpark of a factor of four improvement.

Addendum 2

Henri Menke pointed on in the comments that we ned to add

\pgfplotsset{compat=1.12}

or higher to activate the lua backend in pgfplots. Being a ConTeXt user, I always assumed that pgfplot always uses the latest version and I needed to add compat=something only if I wanted to use old syntax. It is actually the other way round! Like most LaTeX package, pgfplots errs on the side of being more conservative and uses newer features if they are explicitly activated. Adding:

\pgfplotsset{compat=newest}

reduces the runtime of the vanilla version to 0.144s, which is a factor of three faster than my handwritten lua code, and thus almost a factor of ten faster than the old code. So, the simple way to speed up pgfplots is to add \pgfplotsset{compat=1.12} or higher in your code!

I’ll leave this post here as a reminder to RTFM.


This entry was posted in Tikz and tagged tikz, luatex, efficiency.