Skip to content

PERF: Let FreeType read font data from memory instead of through Python#32064

Open
scottshambaugh wants to merge 2 commits into
matplotlib:mainfrom
scottshambaugh:font_mmap
Open

PERF: Let FreeType read font data from memory instead of through Python#32064
scottshambaugh wants to merge 2 commits into
matplotlib:mainfrom
scottshambaugh:font_mmap

Conversation

@scottshambaugh

@scottshambaugh scottshambaugh commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

PR summary

FT2Font currently re-reads data from the font file on every glyph load, costing 10 lseek and 4 read syscalls per glyph on every FT2Font.set_text call. This is still relatively fast on a local filesystem, but imposes huge IO strain on remote filesystems (WSL's /mnt/c bridge to windows in my particular case, but also network mounts).

This PR mmaps the font file and hands the buffer to FreeType, so glyph loads skip the Python layer and only touch the filesystem once. Python still handles opening the file for unicode path handling, with a test added for that.

Note that we build freetype with meson's auto_features=disabled, so its mmap=auto line was a noop and removed in this PR. It doesn't matter here since we're handing freetype the data instead of a path.

Measuring syscalls directly on the font file for 30 glyphs across 10 set_text calls, we drop from ~2900 lseeks and ~1200 reads to 1 lseek and 1 mmap. When using the local linux filesystem on my machine, set_text is ~15x faster and a savefig on an empty plot is ~1.5x faster. When I cross the WSL filesystem boundary, the speedups are ~1000x for set_text and ~10x for savefig.

import matplotlib.pyplot as plt
for _ in range(50):
    fig, ax = plt.subplots()
    fig.savefig("/dev/null", format="png")
    plt.close(fig)

Before (Across WSL filesystem boundary):

image

After:

image

AI Disclosure

Discovered myself, investigated and prototyped with claude code, manually reviewed / edited.

PR checklist

@tacaswell

Copy link
Copy Markdown
Member

How does this intersect with a (hot) disk cache? What is the memory hit (discussed this in person with @ksunden ).

@scottshambaugh

scottshambaugh commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

My understanding of how this works is that the disk cache is on the kernel side, and doesn't help with the userspace calls to fetch that data (much less the python overhead). I believe the reason the WSL bridge/network drives are so much slower here is because they can't have a disk cache. mmap forces that cache to happen, and skips the kernel for direct memory reads, so there's no additional memory usage (for local filesystems) or syscall/python overhead (for either). If the file changes underneath while we're running then the values we've already loaded to ram will be stale, but I don't think that's behavior we need to protect for.

Measuring it, DejaVuSans is ~738kB on disk, and for the ascii character set mmap lazily loads only ~292kB of this into ram (mostly headers). But again, I think this memory usage is just the disk cache made explicit and not actually additional. It's a free win AFAICT.

Comment thread src/ft2font_wrapper.cpp
// loads read from it directly rather than routing through the Python
// file object.
py::object data;
try {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not available on WASI. Is the fallback sufficient here or do we need to keep the old code-path around for that case?

@scottshambaugh scottshambaugh Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback is still solves the issue relative to the old method because it loads the whole contents of the file into memory (rather than mmap's selective loading).

But, I didn't realize mmap wasn't available on some platforms. Updated with an import check, and a test for that fallback path.

@tacaswell

Copy link
Copy Markdown
Member

@QuLogic How do we get the WASM tests to run?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants