How to time code execution in TeX?

Throughout time, during my conversations with members of TeX Exchange, I ran across a few ways to time execution of the code. But the pieces of this information are scattered throughout my account's activity log. To save time and to have a dedicated place for the purpose of timing TeX code, I ask this question.

Above all, I would like to know whether there are plain TeX solutions, or at least LaTeX 2ε solutions, so I would prioritize them over LaTeX3.

There's a \benchmark{<n>}{<code>}, which will run <code> <n> times and measures the elapsed time. The overhead of the loop is minimal, so you usually don't need to worry about that.

There's also a \benchmarkTIC and a \benchmarkTOC command. \benchmarkTIC will reset the timer, and \benchmarkTOC will measure the time since the last \benchmarkTIC and will print it to the terminal.

This is mostly copied from here, but slightly improved and with added tic/toc commands. All of this is just a subset of the features of l3benchmark, so I suggest you use that instead. l3benchmark has a \benchmark:n command which automatically fires out the number of repetitions necessary, and also has \benchmark_tic: and \benchmark_toc: commands which allow nesting (the ones in the code below don't).

The code below prints this to the terminal:

> 1000 feature tests done (0.976s)
> Benchmark TIC:
> Benchmark TOC: 0.97699s


The essence is using the pdfTeX primitives \pdfresettimer and \pdfelapsedtime. The first bunch of lines is just a basic setup so that the code works in more engines. LuaTeX doesn't have the primitives, so their behaviour is emulated with Lua function calls. XeTeX and ε-(u)pTeX have the same primitives under the names \resettimer and \elapsedtime (which makes sense, since they are not PDF-related things). The code normalises eveything to \resettimer and \elapsedtime.

\resettimer resets an internal (let's call it) timer in the engine to zero, and \elapsedtime retrieves the value from that timer (in scaled seconds: 1 s × 65536).

\benchmark does \resettimer at the very beginning, loops the code as many times as requested, and pretty-prints \elapsedtime at the end. \benchmarkTIC and \benchmarkTOC are just wrappers around \resettimer and \elapsedtime to write stuff to the terminal and convert the time from scaled seconds to seconds.

\begingroup
\ifcase 0%
\expandafter\ifx\csname pdfresettimer\endcsname\relax\else1\fi % pdfTeX
\expandafter\ifx\csname directlua\endcsname\relax\else2\fi % LuaTeX
\expandafter\ifx\csname resettimer\endcsname\relax\else3\fi % Others
\relax
\errmessage{Primitive support for timing unavailable}
\or \endgroup
\let\resettimer\pdfresettimer
\let\elapsedtime\pdfelapsedtime
\or \endgroup
\def\resettimer{\directlua{pdfelapsedtimer_basetime = os.clock()}}
\def\elapsedtime{\directlua{tex.print((os.clock()-pdfelapsedtimer_basetime)*65536)}}
\or \endgroup
\fi
\catcode@=11
\let\benchmark@reset\resettimer
\let\benchmark@elapsed\elapsedtime
\begingroup
\def\benchmark@time@s{\strip@pt\dimexpr\benchmark@elapsed sp\relax s}
\def\benchmark@term{\immediate\write17}
\edef\x{\endgroup\def\noexpand\rem@pt##1.##2\detokenize{pt}}%
\x{#1\ifnum#2>\z@.#2\fi}
\def\strip@pt{\expandafter\rem@pt\the}%
\def\benchmark@output#1{\benchmark@term{> #1 feature tests done (\benchmark@time@s)}}
\long\def\benchmark@runNtimes#1#2{%
\ifnum#1>0\relax\expandafter\benchmark@Ntimes@aux\number\numexpr#1-1;{#2}\fi}
\long\def\benchmark@Ntimes@aux#1;#2\fi{\fi #2\benchmark@runNtimes{#1}{#2}}
\long\def\benchmark#1#2{%
\benchmark@reset \benchmark@runNtimes{#1}{#2}%
\benchmark@output{#1}}
\def\benchmarkTIC{\benchmark@term{> Benchmark TIC:}\benchmark@reset}
\def\benchmarkTOC{\benchmark@term{> Benchmark TOC: \benchmark@time@s}}
\catcode@=12

% Just for the example
\input tikz.tex

% Run some code a number of times
\benchmark{1000}
{\setbox0\hbox{\tikz \draw (0,0) -- (1cm, 1cm);}}

% Benchmark some piece of code
\benchmarkTIC
\count0=0
\loop
\setbox0\hbox{\tikz \draw (0,0) -- (1cm, 1cm);}

• @bp2017 :-) You don't need to use expl3, but it feels a lot like reinventing a wheel – Phelype Oleinik Nov 15 '19 at 18:15