|
Slicer 5.13
Slicer is a multi-platform, free and open source software package for visualization and medical image computing
|
Overlaps the work the operating system does when the application's shared libraries are first touched. More...
#include <Base/QTApp/qSlicerStartupFilePrefetcher.h>
Classes | |
| struct | Report |
| What the prefetch did during this startup. More... | |
Static Public Member Functions | |
| static QString | cacheFilePath () |
| static void | finish () |
| static QStringList | loadedLibraryPaths () |
| static Report | report () |
| static QStringList | reportLines () |
| static void | start () |
Overlaps the work the operating system does when the application's shared libraries are first touched.
On Windows, the first time a shared library is mapped after it has been created or modified (which includes the first launch after an installation, and the first after a reboot, because the scan verdict cache does not survive one), the anti-malware filter driver scans the whole file before the mapping completes. The scan is CPU bound and dwarfs the cost of reading the file.
The operating system loader maps libraries one at a time, on the thread that needs them, so those scans are serialized even though the scanning service is perfectly happy to run them in parallel.
This class recovers that parallelism. On a small pool of background threads it reads each library the application is about to need from beginning to end and throws the contents away. Nothing is mapped and no module is created, so no imports are resolved, no relocations applied, no initializer run, and the library does not join the module list. The only lasting effect is that the operating system has read the file, and whatever it does on first sight of one – on Windows, scanning it – is done, so the real load no longer waits for it.
Measured over 8 cold startups of an installed Slicer, each on a freshly copied tree so that every file was new to the scanner, anticipating the 278 libraries (62.7 MB) that startup goes on to load takes 1.8 seconds of background work and saves about 12 seconds: startup drops from 25.1 to 12.8 seconds, of which module registration, instantiation and loading drop from 11.2 to 4.1 seconds. What remains is mostly out of reach: 229 libraries are mapped by the loader before main() runs, and no list can anticipate those.
On macOS, measured over 20 copies of an installed Slicer, each launched once before a reboot so that the checks of a first launch were already paid, and once after it, ten with the prefetch on and ten with it off: startup drops from 7.30 to 6.92 seconds, and the two groups do not overlap. All of the saving is after main() – module registration is 154 ms shorter, instantiation 119 ms and application initialization 93 ms – while the phase before the entry point is 22 ms longer, the background reads competing for a cache that is already warm for what the loader maps. The first launch of a freshly copied tree spends about 10 seconds before main() on signature checks, but that is paid once per file and survives a reboot, so a later run has nothing there left to recover.
The whole file is read rather than a prefix of it, and reading is used rather than mapping the file as a data file, because neither alternative measured any better. Reading the first megabyte of each library touches 46 MB instead of 62.7 MB and costs the same, because the scanner reads the whole file as soon as it is touched at all. What is being bought is a scan verdict, not a warm file cache, so how much is asked for barely matters.
Which libraries to prefetch is not guessed from the directory layout: start() replays the list recorded by finish() during the previous run. That covers loadable modules, Python extension modules, Qt plugins and extensions wherever they are installed, and never touches a library the application does not actually use. The first run after installation has nothing to replay and is not accelerated.
What the prefetch did is recorded in a Report as it goes, and can be printed once startup is complete with the –report-startup-timing command-line option.
The mechanism is the same on every platform, but what it recovers is not, so it only runs by default where it is known to pay:
SLICER_STARTUP_FILE_PREFETCH overrides that per run: 1 turns it on, 0 turns it off. SLICER_STARTUP_FILE_PREFETCH_THREADS sets the size of the pool instead of it being chosen from the number of cores. The class is left out of the build entirely when Slicer_BUILD_STARTUP_FILE_PREFETCH is off.
Typical usage, from the application entry point:
Definition at line 108 of file qSlicerStartupFilePrefetcher.h.
|
static |
Path of the file that holds the list of shared libraries recorded by finish().
It sits next to the revision-specific settings file, named after it, because that is the application's existing answer to where this installation may write: the application's own directory when Slicer_STORE_SETTINGS_IN_APPLICATION_HOME_DIR is on, and the user profile when it is off, which is the configuration meant for installations whose directory is read-only. The path is worked out from the running executable rather than asked of qSlicerCoreApplication::slicerRevisionUserSettingsFilePath(), which resolves to the same place, because start() runs before there is an application to ask.
Keying the name on the revision keeps two installations that share one user profile from replaying each other's lists. Wherever the file ends up, the libraries inside the application directory are recorded relative to it, following the same convention as qSlicerCoreApplication::toSlicerHomeRelativePath(), so the list survives the tree being moved or renamed.
Returns an empty string if the application directory cannot be determined.
|
static |
Record, for the next run, the shared libraries that were loaded since start() was called, and release the background threads. Returns immediately: reads that have not been issued yet are left to complete on their own, because they still benefit the libraries the application loads on demand later on.
Call once startup is complete. Calling it without a preceding start() does nothing.
|
static |
Full paths of the shared libraries currently loaded in this process, in no particular order. Returns an empty list on platforms other than Windows.
|
static |
Snapshot of what the prefetch has done so far. Meaningful at any point, but meant to be taken after finish(), when everything but the tail of the background work is accounted for.
|
static |
report() rendered as one line per fact, ready to be logged. The lines carry no timestamp or severity of their own, so that whoever prints them decides that.
|
static |
Start reading, on background threads, the shared libraries that the previous run of the application loaded after this point. Returns immediately; the work continues in the background.
Also records which shared libraries are already loaded, so that finish() can tell the ones the loader mapped before the application got a chance to run (which are therefore not worth anticipating) from the ones loaded during startup.
Meant to be the first statement of main() or WinMain(): every library mapped between here and finish() is one the next run can anticipate, so anything done before this call is coverage given away. It takes no argument and depends on neither QCoreApplication nor qSlicerCoreApplication, neither of which exists that early; the application name and directory are derived from the running executable.