Repository navigation
Reuse generated classes from a cache in nwbRead and generateCore - #926
Draft
ehennestad wants to merge 4 commits into
Draft
ehennestad wants to merge 4 commits into
ehennestad wants to merge 4 commits into
Conversation
ehennestad
added this pull request to stack #928
October 4, 2026 11:19
This was referenced Oct 4, 2026
generateCore and nwbRead now keep the classes they generate in a cache folder, one entry per namespace name and version, and copy an entry into the save folder instead of generating again when it still matches. Reading a file whose schema version was generated before takes about 1 s instead of 5-8 s. An entry matches when its cache key does. The key hashes the parsed specification, the generator source (+file, +spec, +schemes) and the keys of the namespace's dependencies. Hashing the parsed values, not the source text, makes the vendored YAML and the JSON that matnwb embeds in files produce the same key. The cache folder defaults to generated-types-cache in the matnwb root and can be set with the "GeneratedTypesCacheFolder" preference in the "matnwb" group. Before a namespace is generated, its folder in +types is now emptied, so classes that only the previously generated version defines do not remain, and do not end up in the cache entry. generateExtension and nwbInstallExtension still generate directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each cache entry holds a +types folder. With the entries directly in generated-types-cache, adding matnwb with addpath(genpath(...)), as the intro tutorial shows, put every cached version of every namespace on the path. The entries are now in generated-types-cache/resources/<namespace>/ <version>. genpath skips folders named "resources", so only the empty generated-types-cache folder is added. The resources level is added under a cache folder set with the GeneratedTypesCacheFolder preference too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cache entry was keyed by namespace name and version, so two variants of one version replaced each other: the copy of a schema bundled with matnwb and the copy another writer embedded in a file, an extension generated against two core versions, or the generator on two branches. Reading files of both kinds in turn regenerated the classes each time. Entries now live in resources/<namespace>/<version>/<key>, where <key> is the first 12 characters of the cache key, so the variants are kept side by side. Copying from an entry or writing it records the time of use. Writing a fourth entry for a namespace version removes the one used least recently, which bounds the size of the cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ehennestad
force-pushed
the
cache-generated-types
branch
from
October 6, 2026 18:09
40fff07 to
667aa3a
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Problem — A user who reads NWB files with
nwbReadwaits for MATLAB classes to be generated on every call, even when the file uses the same schema version as the previous one. Reading the same 2.11.0 file three times in a row takes 6.8 s, 4.7 s and 4.3 s; only the first read needs new classes.nwbReadregenerates all classes from the specification embedded in the file each time, and the classes it writes are identical to the ones it wrote on the previous call. Most of the time spent reading a small file is this regeneration.Solution — Generated classes are kept per namespace version and copied back when the specification, the generator and the dependencies are unchanged. The second and third reads of the same file now take about 1 s each.
generateCoreuses the same cache.Additional fix: classes of the previously generated version remain
Problem — When a user generates one schema version and then another, classes that only the first version defines stay in
+types. AftergenerateCore("2.11.0")followed bygenerateCore("2.7.0"),types.core.EventsTable, a type that 2.7.0 does not have, is still on the path. Generation writes the classes of the new version over the old folder but never removes the files the new version does not write. Nothing reports it, so the user can create objects of a type that does not exist in the active schema version.Solution — The folder of a namespace is emptied before its classes are generated, so only the classes of the new version remain.
Why in this PR — The cache stores a copy of the folder right after generation. Without emptying it first, the cache entry for 2.7.0 would permanently contain
EventsTablefrom 2.11.0, and every later switch to 2.7.0 would restore it.What changed
nwbReadandgenerateCorereuse classes generated earlier for the same namespace version, when the specification, the generator code and the dependencies match.generated-types-cachein the MatNWB folder (gitignored). TheGeneratedTypesCacheFolderpreference in thematnwbgroup moves it elsewhere.addpath(genpath(...)), as the intro tutorial shows, does not put the cached classes on the path.generateExtensionandnwbInstallExtensionare unchanged and still generate every time.Implementation notes
matnwb.internal.typecache.generateNamespacestakes parsed namespaces (fromspec.generate), sorts them by dependency, and for each one copies the cache entry into the save folder on a hit, or generates into the save folder and replaces the entry on a miss.<cache folder>/resources/<namespace>/<version>/<key>/, where<key>is the first 12 characters of the cache key. It holds+types/+<namespace>/,namespace.mat(the same content asnamespaces/<name>.mat),record.jsonwith the full key, andlast-used.txt. The record is written last, so an entry without it counts as incomplete.last-used.txt, as text, because file modification times can be too coarse to order entries used in quick succession. Writing a fourth entry for a namespace version removes the least recently used one; an incomplete entry counts as least recently used.resourceslevel keeps the entries out ofgenpath, which skips folders with that name; each entry holds a+typesfolder that would otherwise shadow the active classes.+file,+specand+schemes, and the keys of the dependencies.Examples
Reading the same file repeatedly
The snippet writes an empty 2.11.0 file and reads it three times in a fresh MATLAB session. Each line is the time for one
nwbReadcall.Before — Every read regenerates the classes, so each one takes several seconds.
After — The first read generates the classes and stores them. The next two copy them from the cache.
Switching schema version
The snippet generates 2.11.0, then 2.7.0, and checks whether the class file for
EventsTable, which only 2.11.0 defines, is still there.Before — The active version is 2.7.0, but the 2.11.0 class is still present.
After — Only the classes of 2.7.0 remain.
Switching between files written by PyNWB and by MatNWB
The snippet reads a 2.11.0 file written by PyNWB 4.2.0 and a 2.11.0 file written by MatNWB, twice each, after
generateCore("2.11.0"). The two files embed different variants of the 2.11.0 specification.After — The PyNWB variant is generated once. Each later switch copies the matching variant from the cache. (Before this change every read regenerated, as in the first example.)
How to test
Run the "Reading the same file repeatedly" snippet above in a fresh MATLAB session: the second and third reads should take about 1 s. Then run the new unit tests, which use a small test schema and a temporary cache folder:
Checklist
fix #XXwhereXXis the issue number?🤖 Generated with Claude Code