8

Is there any benchmarking regarding which packages take longer to load than others?

Of course I know it depends on the computer you are running and perhaps even your Internet connection if you are loading them "on the fly", but that's why I'm asking for a benchmark... Has anyone actually taken the time to measure what packages take more time to load? Could this be considered a good point to speed up my work?

Ingmar
  • 6,690
  • 5
  • 26
  • 47
Mario S. E.
  • 18,609

4 Answers4

6

I'm not sure why so many people are opposed to the idea of benchmarking in LaTeX. This seems totally reasonable and is something I've done to identify the bottlenecks in my own TeX code.

Fortunately, pdfTeX comes with two new handy primitives to control timing, \pdfresettimer and \pdfelapsedtime. The former resets the internal timer to 0 whereas the latter provides read-only access to the internal timer. The units are 1/65536 of a second.

Here's a simple macro that uses the fp package to convert the time into milliseconds.

\RequirePackage{fp}
\newcount\timer
% \Time\cs{thing to time}
\newcommand\Time[2]{%
        \pdfresettimer
        #2%
        \timer=\pdfelapsedtime
        \FPdiv#1{\the\timer}{65.536}%
        \FPtrunc#1#1{3}%
}

As the comment indicates, you use \Time\foo{\usepackage{blah}} to set \foo to the time in milliseconds it took to perform the code in the second argument.

It's possible to time \documentclass as well by putting the macro definition before \documentclass (hence the \RequirePackage).

Here's a complete example showing the timing of loading the article class and several packages.

\RequirePackage{fp}
\newcount\timer
% \Time\cs{thing to time}
\newcommand\Time[2]{%
        \pdfresettimer
        #2%
        \timer=\pdfelapsedtime
        \FPdiv#1{\the\timer}{65.536}%
        \FPtrunc#1#1{3}%
}

\Time\documentclasstime{\documentclass{article}}
\Time\tikztime{\usepackage{tikz}}
\Time\siunitxtime{\usepackage{siunitx}}
\Time\captoftime{\usepackage{capt-of}}
\Time\booktabstime{\usepackage{booktabs}}
\Time\graphicxtime{\usepackage{graphicx}}
\Time\hyperreftime{\usepackage{hyperref}}
\Time\mathptmxtime{\usepackage{mathptmx}}

\begin{document}

\begin{tabular}{@{}lS@{}}
\toprule
Code & {Time (\si{\milli\second})}\\
\midrule
\verb!\documentclass{article}! & \documentclasstime \\
\verb!\usepackage{tikz}!       & \tikztime \\
\verb!\usepackage{siunitx}!    & \siunitxtime \\
\verb!\usepackage{capt-of}!    & \captoftime \\
\verb!\usepackage{booktabs}!   & \booktabstime \\
\verb!\usepackage{graphicx}!   & \graphicxtime \\
\verb!\usepackage{hyperref}!   & \hyperreftime \\
\verb!\usepackage{mathptmx}!   & \mathptmxtime \\
\bottomrule
\end{tabular}

\end{document}

enter image description here

It would likely not be too difficult to use etoolbox's \patchcmd to patch \usepackage to perform the timing automatically, but I haven't done so here.

Similarly, it would be easy to create an environment rather than a command. This would have the advantage of not tokenizing the argument. But if you're going to go that route, \pdfresettimer and \pdfelapsedtime are just as easy to use directly.

One caveat with timing \usepackage is if you want to time multiple packages, one of which includes the other (or both include a common third package), then the order of your timing is going to matter. E.g., timing \usepackage{siunitx} followed by timing \usepackage{expl3} shows that expl3 uses almost no time whereas siunitx takes significant time. Conversely, timing them in the opposite order shows that both take a moderate amount of time. tikz and graphicx have similar behavior. graphicx takes about 4 ms on my machine when loaded before tikz and as you can see from the table, about 1/100 of that when loaded afterward.

egreg
  • 1,121,712
TH.
  • 62,639
3

As noted in the answer by TH., one can benchmark from 'within' the TeX run using primitives available in pdfTeX/(u)pTeX, or by emulating the same in LuaTeX. Doing the same in XeTeX is not possible: one has to use system tools wrapped 'around' the TeX run. There will reveal some details, but focussing on package loading is probably the wrong place to worry. (Aside: the LaTeX team have some experimental code for profiling, but it's not currently released.)

One operation which takes a lot of time is opening separate files. For the same code, it's usually the case that a single file loads faster than lots of separate ones. This is one reason why 'custom' format files are loaded fast: see for example Ultrafast PDFLaTeX with precompiling.

What makes a lot more practical difference is how careful code is in 'tight' situations, such as looping. Over time, that is something I've worked on in my own code and can be tricky to get right. For example, the LaTeX team have worked on the performance of expl3, and that has had a knock-on for anyone using the code.

Joseph Wright
  • 259,911
  • 34
  • 706
  • 1,036
2

LaTeX packages are just text files. Once you have them on your computer, all are accesed the same way.

The more a package can do, the more code is involved and hence the file will be bigger. That means as well a longer time to load. On the other hand, packages load other packages to use their functions. Package capt-of has 33 lines of code, most of it commentaries, whereas package siunitx has over a thousand lines of code and requires the whole L3 kernel.

So instead of benchmarking, look at what the package can do and how it is implemented.

Johannes_B
  • 24,235
  • 10
  • 93
  • 248
  • 3
    "instead of benchmarking": I'd say this request is a perfectly sensible one. Most users would like to know which packages are taking the longest to compile in. The suggested alternative to "look at what the package can do..." doesn't seem practical for most users. – LondonRob Jun 27 '17 at 15:20
  • @LondonRob LaTeX is just opening and reading the files. What takes time is producing the pdf by using that read in macros. What really is critical is the length of the document. – Johannes_B Jun 27 '17 at 15:53
  • Loading packages is indeed a performance concern. See this question for an example. – LondonRob Jun 27 '17 at 15:57
  • @LondonRob Apples and oranges. – Johannes_B Jun 27 '17 at 16:01
  • @LondonRob Yes, but the big bottleneck is not how the files are structured internally but how many files there are. For example, expl3 used to be lots of files, we made it into only two partly for this reason. – Joseph Wright Jun 27 '17 at 16:32
  • I pretty strongly disagree with the notion that one should not benchmark. Benchmarking is the only reliable way to determine how long something will actually take. Visual inspection is a sure-fire way to end up with premature optimizations. – TH. Jun 27 '17 at 16:49
  • @TH. Sure, but the real tiime spent in a normal (not MWE) document is the typesetting. That is why i do think there is no point in comparing package load time. – Johannes_B Jun 27 '17 at 16:54
  • Ah, okay. I agree with that. – TH. Jun 27 '17 at 16:57
1

Actually, LaTeX already provide some hooks to add some code to be executed on package load:

\makeatletter
\AddToHook{package/before}{\wlog{::::start loading \@currname : \the\pdf@elapsedtime}}
\AddToHook{package/after}{\wlog{::::done loading \@currname : \the\pdf@elapsedtime}}

Need package pdftexcmds for \pdf@elapsedtime (for portability between different engines)

Then just search for the lines with :::: in the log file.

However... after getting the benchmark result, how to visualize them nicely is a bit nontrivial because there are so much content.


I wrote a small Python script to visualize the result (okay, use the right tool for the job. I could have written it in expl3 but what's the point?)

input_log_file_path="/path/to/a.log"
output_file_name="/path/to/b.tex"

^^ fill in those 2. remember to escape `' if you use them

extra feature: \wlog{::::mark something: \the\pdf@elapsedtime}' adds a marker with contentsomething'

time_scale=100 colors=["green!30!white", "blue!30!white", "red!30!white"] first_coordinate=0.5 box_width=1.5 layer_separation=2 time_axis_length=27

import functools lines=open(input_log_file_path, encoding='latin1').read() printy=functools.partial(print, file=open(output_file_name, "w", encoding='u8'))

def set_globals_locals(globals, locals)->None: #if both are originally None #return caller's parent frame's globals and locals import sys # because of a Python bug https://bugs.python.org/issue21161 let's pass both as globals out=sys._getframe(1).f_locals if globals is None and locals is None: f=sys._getframe(2) return {f.f_globals, f.f_locals}, None else: pass return globals, locals

def evalz(s: str, globals=None, locals=None, escape_char=None)->None: globals, locals=set_globals_locals(globals, locals)

if escape_char is None:
    escape_char=next(ch for ch in "%!@" if ch in s)

parts=s.split(escape_char)
result=[]
for i in range(len(parts)):
    if i%2==0:
        result.append(parts[i])
    elif parts[i]=="": # %% → %
        result.append(escape_char)
    else:
        try:
            item=eval(parts[i], globals, locals)
        except:
            import sys
            printy("Error while executing code ========\n"+ parts[i] + "\n========", file=sys.stderr)
            raise
        result.append(
                item if isinstance(item, str) else
                str(item) if isinstance(item, int) else
                __import__("sympy").latex(item)
                )
return "".join(result)

def printz(s: str, globals=None, locals=None, escape_char: str=None, end: str=None)->None: globals, locals=set_globals_locals(globals, locals) printy(evalz(s, globals, locals, escape_char=escape_char), end=end)

printz( r"""%! TEX program = lualatex \documentclass{article} \usepackage{tikz} \usepackage[a4paper, margin=0.5cm]{geometry} %\usepackage{tooltip} %\usepackage{pdfcomment} \begin{document}

\begin{tikzpicture}[yscale=-1] \draw [->] (0, 0) -- (0, $time_axis_length$); \def\a#1#2{ \draw (0.1, #1) -- (-0.1, #1) node [left] {#2}; }

""" , escape_char="$")

import re items=[] mark_event=object() for line in lines.splitlines(): if not line.startswith("::::"): continue match=re.fullmatch(r"::::(?:(start|done) loading ([a-zA-Z0-9-]+)|mark (.*)): (\d+)", line) assert match, line moment: float=int(match[4])/65536 if match[3]: items.append((mark_event, match[3], moment)) else: is_start: bool=match[1]=="start" package_name: str=match[2] items.append((is_start, package_name, moment))

initial_skip=items[0][2] def time_to_coordinate(t: float)->float: return (t-initial_skip)*time_scale

for t in range(0, 50): t/=50 if t>=initial_skip and time_to_coordinate(t)<=time_axis_length: printz(r"\a{%time_to_coordinate(t)%}{%t%}")

mark_last_y=time_to_coordinate(-1) mark_y_separation=0.25

package_stack=[] for is_start, package_name, moment in items: if not is_start and not package_stack: continue

if is_start is mark_event:
    layer=len(package_stack)

    red_line_left=first_coordinate+layer_separation*(layer-1)+box_width
    red_line_right=red_line_left+box_width/2
    y_coordinate=time_to_coordinate(moment)

    node_y_coordinate=mark_last_y=max(mark_last_y+mark_y_separation, y_coordinate)

    printz(
    r&quot;&quot;&quot;\draw[red] (%red_line_left%, %y_coordinate%)
            -- (%red_line_right%, %y_coordinate%);
    \path (%red_line_right%, %node_y_coordinate%) node [black, right] {\small %package_name%};
    &quot;&quot;&quot;
    )
elif is_start:
    package_stack.append((package_name, moment))
else:
    package_name1, start_moment=package_stack.pop()
    assert package_name1==package_name

    layer=len(package_stack)

    #package_name_t=evalz(r&quot;\pdftooltip{t}{%package_name%}&quot;)
    package_name_t=package_name

    color=colors[layer%len(colors)]

    printz(
    r&quot;&quot;&quot;\fill[%color%] (
        %first_coordinate+layer_separation*layer%,
        %time_to_coordinate(start_moment)%
    ) rectangle 
    (
        %first_coordinate+box_width+layer_separation*layer%,
        %time_to_coordinate(moment)%
    ) node [black, midway] {\small %package_name_t%};
    &quot;&quot;&quot;
    )





printy( r""" \end{tikzpicture}

\end{document} """ )

Run the script, then compile the generated TeX file.

Output may look like this

example output

(turns out my packages doesn't load that slow, relatively speaking. And why is the gap between packages so large?)

user202729
  • 7,143