Repository navigation
lua: Cache loaded modules to disk as binary chunks - #769
Open
arch1t3cht wants to merge 4 commits into
Open
arch1t3cht wants to merge 4 commits into
arch1t3cht wants to merge 4 commits into
Conversation
arch1t3cht
force-pushed
the
lua-load-cache
branch
2 times, most recently
from
October 31, 2026 23:40
6b8d3ea to
20ddd12
Compare
arch1t3cht
force-pushed
the
lua-load-cache
branch
from
November 1, 2026 18:23
20ddd12 to
deb4374
Compare
arch1t3cht
force-pushed
the
lua-load-cache
branch
4 times, most recently
from
November 5, 2026 11:44
be0efe6 to
e1df8ea
Compare
arch1t3cht
marked this pull request as ready for review
November 5, 2026 11:55
arch1t3cht
force-pushed
the
lua-load-cache
branch
from
November 5, 2026 12:01
e1df8ea to
7c09a1f
Compare
Prepare to add more functions to the anonymous namespace without too much extra indentation.
boost::filesystem's unique_path was not ported over to std::filesystem due to security issues, so the migration made agi::io::Save's temp files not use any unique paths at all. This is mostly fine, but when multiple threads are trying to save the same file (as might happen with the Lua blob caching in the following commit), bad things might happen. Hence, give each temp file a name depending on the thread writing them. This is not trying to be resistant to outside attacks, but it prevents multiple Aegisub threads from fighting over the same file.
arch1t3cht
force-pushed
the
lua-load-cache
branch
5 times, most recently
from
November 10, 2026 12:41
7af1017 to
7e429c9
Compare
MSVC's STL will output error codes in the system category rather than the generic category, so the error code values were checked against the wrong enum. The FileNotFound error detection did work before, but only by coincidence since the error's integer value was 2 for both POSIX and Windows.
arch1t3cht
force-pushed
the
lua-load-cache
branch
2 times, most recently
from
November 10, 2026 17:43
09e1186 to
121efac
Compare
With my current version of DependencyControl (0.9.0), any automation
script using DependencyControl imports no fewer than 47 MoonScript
files. Every automation scripts has its own isolated Lua environment, so
every script needs to compile its MoonScript files to Lua individually.
When enough such automation scripts are installed, the MoonScript-to-Lua
conversion alone makes up more than 90% of the CPU time when launching
Aegisub or rescanning the autoload folder.
Improve this situation by caching the binary blobs of loaded Lua or
MoonScript modules to the disk.
Some alternative approaches that were considered:
- Only caching the loaded chunks (or Lua transpile output) for MoonScript
files and not also for Lua files.
-> Benchmarks showed that even for Lua files, the time saved by the
caching outweighs the cost of the additional file IO, see below.
- Caching the loaded chunks to memory and not to the disk.
-> This would simplify some of the cache invalidation logic, but not
improve the load time of the first loaded automation script.
This is already quite significant; see the first-launch vs.
second-launch comparisons below.
- Encouraging script distributors to transpile their MoonScript code to
Lua prior to distribution.
-> Apart from imposing extra effort, this makes local error messages
less useful, makes it harder to locally patch scripts, and
makes it harder to audit downloaded scripts.
- Making DependencyControl transpile the modules it installs to Lua or
chunks.
-> Has many of the same problems as the previous point.
The trickiest part is ensuring that cached chunks are properly
regenerated when the source files (*or* MoonScript) are updated.
Since we have full control over the cache file's format, we can build a
cache key out of not just the modification time, but also the size and
inode number (or some equivalent identifier that detects file moves).
Apart from that, we also need to make sure that the line_tables relating
compiled Lua source lines to MoonScript source lines (which are needed
for proper stack traces in error messages) are also preserved when
loading chunks directly from cache files.
On Windows, the file locking causes some difficulties when multiple
threads are trying to write the same cache file, so there we need to
make sure to not make errors due to locked files bloat the logs or
unnecessarily delay the loading.
Some numbers:
With a typical environment with around 25 DependencyControl-enabled
scripts installed, this results in the following reductions in
start time (wall-clock times via hyperfine on Linux):
- When loading all scripts on a single thread (replacing std::launch::async
with std::launch::deferred in auto4_base.cpp):
- Before this commit: 23.136 s ± 0.134 s
- After this commit, first launch: 4.330 s ± 0.120 s
- After this commit, second launch: 1.468 s ± 0.026 s
-> Up to a 93% reduction
- When loading scripts on multiple threads (current behavior)
- Before this commit: 6.258 s ± 0.209 s
- After this commit, first launch: 4.166 s ± 0.302 s
- After this commit, second launch: 1.944 s ± 0.249 s
-> Up to a 68% reduction
Of course, the percentages will depend on the number of installed
automation scripts.
Note in particular that loading all scripts on one thread is now
*faster* on the second launch than loading them on different threads.
This is probably because the different DependencyControl instances are
fighting for the locks on their config files, but I haven't profiled
this in detail yet.
Caching only the chunks of MoonScript files and not of Lua files results
in a start time of 1.670 s ± 0.030 s on the second launch on one
thread, so also caching Lua files yields around a 12% improvement.
Options for future work:
- Rethink the multithreaded loading of scripts
- Somehow improve DependencyControl's locking, possibly by adding
additional features to the Lua API
- Let automation scripts be loaded in the background instead of blocking
Aegisub's launching on it
Member
Author
|
Should be genuinely ready now. |
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.
With my current version of DependencyControl (0.9.0), any automation
script using DependencyControl imports no fewer than 47 MoonScript
files. Every automation scripts has its own isolated Lua environment, so
every script needs to compile its MoonScript files to Lua individually.
When enough such automation scripts are installed, the MoonScript-to-Lua
conversion alone makes up more than 90% of the CPU time when launching
Aegisub or rescanning the autoload folder.
Improve this situation by caching the binary blobs of loaded Lua or
MoonScript modules to the disk.
Some alternative approaches that were considered:
files and not also for Lua files.
-> Benchmarks showed that even for Lua files, the time saved by the
caching outweighs the cost of the additional file IO, see below.
-> This would simplify some of the cache invalidation logic, but not
improve the load time of the first loaded automation script.
This is already quite significant; see the first-launch vs.
second-launch comparisons below.
Lua prior to distribution.
-> Apart from imposing extra effort, this makes local error messages
less useful, makes it harder to locally patch scripts, and
makes it harder to audit downloaded scripts.
chunks.
-> Has many of the same problems as the previous point.
The trickiest part is ensuring that cached chunks are properly
regenerated when the source files (or MoonScript) are updated.
Since we have full control over the cache file's format, we can build a
cache key out of not just the modification time, but also the size and
inode number (or some equivalent identifier that detects file moves).
Apart from that, we also need to make sure that the line_tables relating
compiled Lua source lines to MoonScript source lines (which are needed
for proper stack traces in error messages) are also preserved when
loading chunks directly from cache files.
On Windows, the file locking causes some difficulties when multiple
threads are trying to write the same cache file, so there we need to
make sure to not make errors due to locked files bloat the logs or
unnecessarily delay the loading.
Some numbers:
With a typical environment with around 25 DependencyControl-enabled
scripts installed, this results in the following reductions in
start time (wall-clock times via hyperfine on Linux):
When loading all scripts on a single thread (replacing std::launch::async
with std::launch::deferred in auto4_base.cpp):
-> Up to a 93% reduction
When loading scripts on multiple threads (current behavior)
-> Up to a 68% reduction
Of course, the percentages will depend on the number of installed
automation scripts.
Note in particular that loading all scripts on one thread is now
faster on the second launch than loading them on different threads.
This is probably because the different DependencyControl instances are
fighting for the locks on their config files, but I haven't profiled
this in detail yet.
Caching only the chunks of MoonScript files and not of Lua files results
in a start time of 1.670 s ± 0.030 s on the second launch on one
thread, so also caching Lua files yields around a 12% improvement.
Options for future work:
additional features to the Lua API
Aegisub's launching on it