Cache compiled protocols on disk across processes - #1516
Conversation
Every process recompiles each protocol state from minecraft-data JSON: loading protocol.json, walking it in addProtocol, generating code and evaling it — and the eval'd result can never hit V8's on-disk compile cache. For a full 26.1 client (four states, both directions) that is ~340ms of setup per process on an idle M-series Mac, repeated by every bot, test child and CLI run. Pass a cacheFile to protodef's compileProtoDefSync (new in protodef) keyed by nmp/protodef/minecraft-data versions plus the protocol key and customPackets, so a stale file is unreachable and never needs explicit invalidation. On a hit, loading the cached module also skips mcData.protocol and addProtocol entirely, and protodef enables the V8 compile cache for the require, cutting protocol setup to ~170ms. Default cache dir is os.tmpdir()/node-minecraft-protocol-cache; NMP_PROTOCOL_CACHE_DIR overrides it, =0 disables. The nbt types are registered before the cache attempt (their natives are needed to load a cached protocol) and again after addProtocol on a miss, preserving the type precedence of the previous ordering — verified by comparing all compiled type functions against the old code modulo generated variable names.
extremeheat
left a comment
There was a problem hiding this comment.
I don't think this is a good idea
Not only is the code hacky, but it creates a dependence on file system writes (issues for prismarine-viewer, prismarine-web-client, other browser based tools) but also creates issues with stale caches, cache invalidation and the program unexpectedly writing to disk on the machine without obvious means to reclaim the space
What you are likely looking for instead is an in-memory cache, something that there should already be
My goal was to optimize runExample which runs out of process so I can’t do an in memory cache, afaik. |
|
340ms is still a fraction of a second only done once/version, I doubt that's what's causing the test delay. >300ms seems rather slow and that itself should be investigated but even despite that for 20 versions it's about 6 seconds in total if not being done in sequence/JITable |
Its done for each runExample (which we have many of) serially. The goal is to shed as many seconds as possible off the test suite but if this isn’t a good idea, it’s fine we can drop this one. |
|
Since the protocol files can get big (we have 3 sets of the read, write and sizeof protocols), writing to and retrieving from the disk takes much more time than reading from memory (like a 1000x difference) |
Draft: depends on ProtoDef-io/node-protodef#175 being merged and released (uses the new
compileProtoDefSync({ cacheFile })/loadCompiledProtoDefSync).Problem
Every process recompiles every protocol state from minecraft-data JSON: load
protocol.json, walk it inaddProtocol, generate code,evalit. The eval'd result dies with the process and is invisible to V8's compile cache. For a full 26.1 client (4 states × both directions) that's ~340ms of protocol setup per process on an idle M-series Mac — paid again by every bot, every test child process, every CLI invocation.Change
Pass a
cacheFileto protodef keyed by the nmp + protodef + minecraft-data versions, protocol key, and a hash ofcustomPackets— everything that determines the generated code — so a stale cache file is simply unreachable and no explicit invalidation exists. On a hit:mcData.protocolis never loaded andaddProtocolnever walks it (those cost as much as compilation itself)Protocol setup for the same 26.1 client drops to ~170ms with warm caches. First run in a clean tmpdir is unchanged (compile + write).
Cache location:
os.tmpdir()/node-minecraft-protocol-cache, override withNMP_PROTOCOL_CACHE_DIR, disable withNMP_PROTOCOL_CACHE_DIR=0. Any read/write failure falls back silently to in-process compilation.Ordering note
The nbt types are now registered before the cache attempt (their natives are needed to load a cached protocol) and again after
addProtocolon a miss. This preserves the previous type precedence (the nbt schemas override types the protocol declares native) — verified by diffing every compiled type function (246 types × read/write/sizeOf) against the old ordering: identical modulo protodef's nondeterministic generated variable names.Verification
test/packetTest.js: 5199 passing on both the cold (cache-writing) and warm (cache-loading) paths;test/cyclePacketTest.js: 1014 passing; standard clean.