Speeding up pdfTeX

2025-06-26 / 2025-09-08

Writing TeX is an integral part of my studies. I usually have Vim and zathura open side-by-side and a simple script recompile the TeX source file on changes. With plain TeX and several dozen custom macros, each compile takes about 160 milliseconds.

When battery-powered, however, my laptop does some weird powersave thing that doubles the compile times, and over 300 milliseconds of latency is very distracting. How can I speed up pdfTeX?

I started off with its source code, which only made me confused. The pdfTeX binary on my system is stripped, and I know very little about gdb or profiling, so that is not gonna work either.

This stayed in the back of my mind for a few weeks, until I realized that, well, pdfTeX is not a very fancy program: it simply reads a bunch of files, does some macro and layout stuff, and produces a PDF file. My copy of pdfTeX happened to be dynamically linked, so I could trace fopen and fclose calls with LD_PRELOAD.

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static struct node {
    struct node *next;
    char *path;
    FILE *fptr;
} *open_files;

static unsigned int time_ns(void) {
    struct timespec t;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t);
    return t.tv_nsec;
}

FILE *fopen(char const *path, char const *mode) {
    static FILE *(*foo)(char const *, char const *);
    if (!foo) foo = dlsym(RTLD_NEXT, "fopen");

    fprintf(stderr, "%09u fopen  %s, %s\n", time_ns(), path, mode);
    FILE *ret = foo(path, mode);

    struct node *head = malloc(sizeof(*head));
    head->next = open_files;
    head->path = strdup(path);
    head->fptr = ret;
    open_files = head;

    return ret;
}

int fclose(FILE *stream) {
    static int (*foo)(FILE *);
    if (!foo) foo = dlsym(RTLD_NEXT, "fclose");

    char *path = "unknown";
    for (struct node *p = open_files; p; p = p->next) {
        if (p->fptr == stream) {
            path = p->path;
            break;
        }
    }

    fprintf(stderr, "%09u fclose %s\n", time_ns(), path);
    return foo(stream);
}

With the above in preload.c and gentle.tex from here:

$ gcc -fPIC -shared -O2 preload.c -o preload.so
$ LD_PRELOAD=./preload.so pdftex gentle.tex >/dev/null
001377884 fopen  /path/to/texlive/texmf.cnf, r
001413741 fclose /path/to/texlive/texmf.cnf
001418630 fopen  /path/to/texlive/texmf-dist/web2c/texmf.cnf, r
001642500 fclose /path/to/texlive/texmf-dist/web2c/texmf.cnf
002040245 fopen  /path/to/texlive/texmf-config/ls-R, r
002054983 fclose /path/to/texlive/texmf-config/ls-R
002058449 fopen  /path/to/texlive/texmf-var/ls-R, r
002113944 fclose /path/to/texlive/texmf-var/ls-R
002117160 fopen  /path/to/texlive/texmf-dist/ls-R, r
064456512 fclose /path/to/texlive/texmf-dist/ls-R
064595393 fopen  ./gentle.tex, r
064602586 fclose ./gentle.tex
064902187 fopen  /path/to/texlive/texmf-var/web2c/pdftex/pdftex.fmt, rb
080705319 fopen  gentle.tex, rb
080718764 fopen  gentle.log, wb
081032953 fopen  /path/to/texlive/texmf-dist/fonts/map/fontname/texfonts.map, r
081059403 fclose /path/to/texlive/texmf-dist/fonts/map/fontname/texfonts.map
081101492 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm, rb
081113404 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm
081146336 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm, rb
081155824 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm
081186211 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm, rb
081194035 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm
081223000 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm, rb
081230253 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm
081262313 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm, rb
081271701 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm
081301226 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm, rb
081310173 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmr10.tfm
081344848 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmbx10.tfm, rb
081352542 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmbx10.tfm
081380695 fopen  /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmbx10.tfm, rb
081388219 fclose /path/to/texlive/texmf-dist/fonts/tfm/public/cm/cmbx10.tfm
081542408 fopen  gentle.pdf, wb
081701166 fopen  /path/to/texlive/texmf-var/fonts/map/pdftex/updmap/pdftex.map, rb
133196806 fclose /path/to/texlive/texmf-var/fonts/map/pdftex/updmap/pdftex.map
159032589 fclose gentle.tex
159423773 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb, rb
159910675 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb
160242698 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb, rb
160534054 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb
160655762 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb, rb
161011600 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb
161197247 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb, rb
161623598 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb
161926666 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb, rb
162242328 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi5.pfb
162363384 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb, rb
162691489 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb
162835419 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb, rb
163397643 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb
163874386 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.pfb, rb
164163889 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr5.pfb
164284325 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb, rb
164634781 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb
164820740 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsl10.pfb, rb
165204609 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsl10.pfb
165432115 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss10.pfb, rb
165704135 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmss10.pfb
165842334 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb, rb
166237475 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb
166515936 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb, rb
166811911 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb
166934270 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb, rb
167316396 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb
167510219 fopen  /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb, rb
167944233 fclose /path/to/texlive/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb
172672187 fclose gentle.pdf
172772595 fclose gentle.log

Just look at this. Noticed something? It took curiously long to process texmf-dist/ls-R (62 ms) and texmf-var/fonts/map/pdftex/updmap/pdftex.map (51 ms). Together, that is over 60% of the total runtime!

But why? Well, they are text files, each over 5 MiB in size. Like, what the fuck, pdfTeX parses ten megabytes of text when it compiles my dozen-kilobyte homework?

So how useful are they? Well, ls-R is a recursive list of all files in texmf-dist, and pdftex.map is a list of all fonts that pdfTeX knows about.

Most of which never gets used at all.

Now, the fix should become obvious. Just create a dummy ls-R and a dummy pdftex.map that only contains what I actually use, and tell pdfTeX to read these instead. You can use \pdfmapfile to make pdfTeX use some other map file than pdftex.map, but you are out of luck for ls-R. There is a simple way though — just check and modify the argument to fopen:

if (!strcmp(path, "/path/to/texlive/texmf-dist/ls-R"))
    path = "/path/to/dummy/ls-R";
if (!strcmp(path, "/path/to/texlive/texmf-var/fonts/map/pdftex/updmap/pdftex.map"))
    path = "/path/to/dummy/pdftex.map";

This makes pdfTeX run ~3x faster, or ~5x for a few pages of homework.

This is of course still not ideal. Startup overhead still dominates the runtime for short documents, and I should probably figure out why my laptop runs at half speed on battery. But this is good enough for me, for now.

It is rather dumb once you figure it out.


Note: I was running Fedora Linux 40 with many texlive packages installed from the package manager when I wrote the original post. In the meantime I switched (back) to Arch Linux and installed texlive-basic and texlive-fontsrecommended only, which made pdfTeX way snappier. The results above were updated with tests with a portable full install of TeX Live, where the problem turned out to be worse.

Copyright © 2025 Vursc. All rights reserved.