The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL). The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls. The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap. Class serialization no longer distinguishes predefined classes. The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.) Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code. The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime. The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration. Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot. TEST=ci Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Tess Strickland <sstrickl@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
This package provides libraries and a utility for analysing the size and
contents of Dart VM AOT snapshots based on the output of
--print-instructions-sizes-to, --write-v8-snapshot-profile-to and
--trace-precompiler-to VM flags.
AOT Snapshot Basics
Dart VM AOT snapshot is simply a serialized representation of the Dart VM
heap graph. It consists of two parts: data (e.g. strings, const instances,
objects representing classes, libraries, functions and runtime metadata) and
executable code (machine code generated from Dart sources). Some nodes in this
graph have clean and direct relationship to the original program (e.g. objects
representing libraries, classes, functions), while other nodes don't. Bitwise
equivalent objects can be deduplicated and shared (e.g. two functions with the
same body will end up using the same machine code object). This makes
impossible to attribute of every single byte from the snapshot to a particular
place in the program with a 100% accuracy.
--print-instructions-sizes-toattributes executable code from the snapshot to a particular Dart function (or internal stub) from which this code originated (ignoring deduplication). Executable code usually constitutes around half of the snapshot, those this varies depending on the application.--write-v8-snapshot-profile-tois a graph representation of the snapshot, it attributes bytes written into a snapshot to a node in the heap graph. This format covers both data and code sections of the snapshot.--trace-precompiler-togives information about dependencies between compiled functions, allowing to determine why certain function was pulled into the snapshot.
Passing flags to the AOT compiler
In both Flutter and dart compile exe / dart compile aot-snapshot you can use
--extra-gen-snapshot-options to pass flags to the AOT compiler:
$ flutter build aot --release --extra-gen-snapshot-options=--write-v8-snapshot-profile-to=profile.json
$ dart compile exe --extra-gen-snapshot-options=--write-v8-snapshot-profile-to=profile.json -o binary input.dart
Similarly with --print-instructions-sizes-to.
If you are working on the Dart SDK you can use the pkg/vm/tool/precompiler2
script, in which case you can just pass these flags directly:
$ pkg/vm/tool/precompiler2 --write-v8-snapshot-profile-to=profile.json input.dart binary
CLI
The command line interface to the tools in this package is provided by a single
entry point bin/analyse.dart. It consumes output of
--print-instructions-sizes-to and --write-v8-snapshot-profile-to flags and
presents it in different human readable ways.
This script can be installed globally as snapshot_analysis using
$ pub global activate vm_snapshot_analysis
snapshot_analysis supports the following subcommands:
summary
$ snapshot_analysis summary [-b granularity] [-w filter] <input-profile.json>
This command shows breakdown of snapshot bytes at the given granularity (e.g.
method, class, library or package), filtered by the given substring
filter.
For example, here is a output showing how many bytes from a snapshot
can be attributed to classes in the dart:core library:
$ pkg/vm/bin/snapshot_analysis.dart summary -b class -w dart:core profile.json
+-----------+------------------------+--------------+---------+----------+
| Library | Class | Size (Bytes) | Percent | Of total |
+-----------+------------------------+--------------+---------+----------+
| dart:core | _Uri | 43563 | 15.53% | 5.70% |
| dart:core | _StringBase | 28831 | 10.28% | 3.77% |
| dart:core | :: | 27559 | 9.83% | 3.60% |
| @other | | 25467 | 9.08% | 3.33% |
| dart:core | Uri | 14936 | 5.33% | 1.95% |
| dart:core | int | 12276 | 4.38% | 1.61% |
| dart:core | NoSuchMethodError | 12222 | 4.36% | 1.60% |
...
Here objects which can be attributed to _Uri take 5.7% of the snapshot,
at the same time objects which can be attributed to dart:core library
but not to any specific class within this library take 3.33% of the snapshot.
This command also supports estimating cumulative impact of a library or a
package together with its dependencies - which can be computed from
a precompiler trace (generated by adding
--trace-precompiler-to=path/to/trace.json to --extra-gen-snapshot-options=).
$ snapshot_analysis summary [-b granularity] [-w filter] [-s dep-tree-start-depth] [-d dep-tree-display-depth] --precompiler-trace=<input-trace.json> <input-profile.json>
For example:
$ snapshot_analysis summary -b package /tmp/profile.json
+-----------------------------+--------------+---------+
| Package | Size (Bytes) | Percent |
+-----------------------------+--------------+---------+
| package:compiler | 5369933 | 38.93% |
| package:front_end | 2644942 | 19.18% |
| package:kernel | 1443568 | 10.47% |
| package:_fe_analyzer_shared | 944555 | 6.85% |
...
$ snapshot_analysis summary -b package -s 1 -d 3 --precompiler-trace=/tmp/trace.json /tmp/profile.json
+------------------------------+--------------+---------+
| Package | Size (Bytes) | Percent |
+------------------------------+--------------+---------+
| package:compiler (+ 8 deps) | 5762761 | 41.78% |
| package:front_end (+ 1 deps) | 2708981 | 19.64% |
| package:kernel | 1443568 | 10.47% |
| package:_fe_analyzer_shared | 944555 | 6.85% |
...
Dependency trees:
package:compiler (total 5762761 bytes)
├── package:js_ast (total 242490 bytes)
├── package:dart2js_info (total 101280 bytes)
├── package:crypto (total 27434 bytes)
│ ├── package:typed_data (total 11850 bytes)
│ └── package:convert (total 5185 bytes)
├── package:collection (total 15182 bytes)
├── package:_js_interop_checks (total 4627 bytes)
└── package:js_runtime (total 1815 bytes)
package:front_end (total 2708981 bytes)
└── package:package_config (total 64039 bytes)
compare
$ snapshot_analysis compare [-b granularity] <old.json> <new.json>
This command shows comparison between two size profiles, allowing to understand changes to which part of the program contributed most to the change in the overall snapshot size.
$ pkg/vm/bin/snapshot_analysis.dart compare -b class old.json new.json
+------------------------+--------------------------+--------------+---------+
| Library | Class | Diff (Bytes) | Percent |
+------------------------+--------------------------+--------------+---------+
| dart:core | _SimpleUri | 11519 | 22.34% |
| dart:core | _Uri | 6563 | 12.73% |
| dart:io | _RandomAccessFile | 5337 | 10.35% |
| @other | | 4009 | 7.78% |
...
In this example 11519 more bytes can be attributed to _SimpleUri class in
new.json compared to old.json.
treemap
$ snapshot_analysis treemap [--format <format>] <input.json> <output-dir>
$ google-chrome <output-dir>/index.html
This command generates treemap representation of the information from the
profile input.json and stores it in output-dir directory. Treemap can
later be viewed by opening <output-dir>/index.html in the browser of your
choice.
--format flag allows to control granularity of the output when input.json
is a V8 snapshot profile, available options are:
collapsedessentially rendersProgramInfoas a treemap, individual snapshot nodes are ignored.simplifiedsame ascollapsed, but also folds size information from nested functions into outermost function (e.g. top level function or a method) producing easy to consume output.data-and-codecollapses snapshot nodes based on whether they represent data or executable code.object-type(default) collapses snapshot nodes based on their type only.
explain
explain dynamic-calls
$ snapshot_analysis explain dynamic-calls <profile.json> <trace.json>
This command generates a report listing dynamically dispatched selectors and their approximate impact on the code size.
snapshot_analysis explain dynamic-calls /tmp/profile.json /tmp/trace.json
+------------------------------+--------------+---------+----------+
| Selector | Size (Bytes) | Percent | Of total |
+------------------------------+--------------+---------+----------+
| set:requestHeader | 10054 | 28.00% | 0.03% |
| get:scale | 3630 | 10.11% | 0.01% |
...
Dynamic call to set:requestHeader (retaining ~10054 bytes) occurs in:
package:my-super-app/src/injector.dart::Injector.handle{body}
Dynamic call to get:scale (retaining ~3630 bytes) occurs in:
package:some-dependency/src/image.dart::Image.==
API
This package can also be used as a building block for other packages which want to analyse VM AOT snapshots.
package:vm_snapshot_analysis/instruction_sizes.dartprovides helpers to read output of--print-instructions-sizes-to=...package:vm_snapshot_analysis/v8_profile.dartprovides helpers to read output of--write-v8-snapshot-profile-to=...
Both formats can be converted into a ProgramInfo structure which attempts
to breakdown snapshot size into hierarchical representation of the program
structure which can be understood by a Dart developer, attributing bytes
to packages, libraries, classes and functions.
package:vm_snapshot_analysis/utils.dartcontains helper methodloadProgramInfowhich automatically detects format of the input JSON file and createsProgramInfoin an appropriate way, allowing to write code which works in the same way with both formats.
Precompiler Trace Format (--write-precompiler-trace-to=...)
AOT compiler can produce a JSON file containing information about compiled functions and dependencies between them. This file has the following structure:
{
"trace": traceArray,
"entities": entitiesArray,
"strings": stringsArray,
}
-
stringsArrayis an array of strings referenced by other parts of the trace by their index in this array. -
entitiesArrayis an flattened array of entities:"C", <library-uri-idx>, <class-name-idx>, 0- class record;"V", <class-idx>, <name-idx>, 0- static field record;"F"|"S", <class-idx>, <name-idx>, <selector-id>- function record (Ffor dynamic functions andSfor static functions);
Note that all records in this array occupy the same amount of elements (
4) to make random access by index possible. -
traceArrayis an flattened array of precompilation events:"R"- root event (always the first element)"E"- end event (always the last element)"C", <function-idx>- function compilation event
Root and function compilation events can additionally be followed by a sequence of references which enumerate outgoing dependencies discovered by the AOT compiler:
<entity-idx>- a reference to a function or a static field;"S", <selector-idx>- a dynamic call with the given selector;"T", <selector-id>- dispatch table call with the given selector id;
Flattened array is an array of records formed by consecutive elements:
[R0_0, R0_1, R0_2, R1_0, R1_1, R1_2, ...] here R0_* is the first record
and R1_* is the second record and so on.
Features and bugs
Please file feature requests and bugs at the issue tracker.