First create a hello.dart
file with the following content.
void main(List<String> args) {
print('Hello, World!');
}
Next we precompile this file using PRODUCT
AOT compiler (equivalent of running flutter build aot --release
).
$ env DART_CONFIGURATION=ProductX64 pkg/vm/tool/precompiler2 -v -Ddart.vm.product=true --write-v8-snapshot-profile-to=/tmp/profile.json /tmp/hello.dart /tmp/snapshot.elf
This should produce /tmp/profile.json
V8 snapshot profile.
We can then analyse it in different ways, for example we can ask to print which packages contributed most bytes to the output:
$ pkg/vm/bin/snapshot_analysis.dart summary -b package /tmp/profile.json
+------------------------+--------------+---------+
| Package | Size (Bytes) | Percent |
+------------------------+--------------+---------+
| dart:core | 280419 | 36.68% |
| dart:typed_data | 139142 | 18.20% |
| @unknown | 53289 | 6.97% |
| dart:collection | 51978 | 6.80% |
| dart:async | 45975 | 6.01% |
| dart:io | 41018 | 5.37% |
| dart:convert | 36971 | 4.84% |
| dart:isolate | 32945 | 4.31% |
| @stubs | 21979 | 2.87% |
| @shared | 21039 | 2.75% |
| dart:_internal | 17491 | 2.29% |
| dart:_builtin | 8081 | 1.06% |
| dart:ffi | 3493 | 0.46% |
| dart:math | 3215 | 0.42% |
| dart:developer | 2036 | 0.27% |
| dart:vmservice_io | 1388 | 0.18% |
| dart:wasm | 1257 | 0.16% |
| dart:cli | 1090 | 0.14% |
| dart:_http | 606 | 0.08% |
| file:///tmp/hello.dart | 536 | 0.07% |
| dart:mirrors | 399 | 0.05% |
| dart:nativewrappers | 117 | 0.02% |
| dart:_vmservice | 32 | 0.00% |
+------------------------+--------------+---------+
Total: 764496 bytes
Breakdown by object type:
+--------------------------+--------------+---------+
| Type | Size (Bytes) | Percent |
+--------------------------+--------------+---------+
| (RO) Instructions | 401744 | 52.55% |
| (RO) _OneByteString | 119392 | 15.62% |
| (RO) CodeSourceMap | 61728 | 8.07% |
| (RO) CompressedStackMaps | 38720 | 5.06% |
| Function | 30538 | 3.99% |
| Array | 27900 | 3.65% |
| Code | 24988 | 3.27% |
| Class | 17024 | 2.23% |
| Type | 8461 | 1.11% |
| int | 6717 | 0.88% |
| ObjectPool | 5393 | 0.71% |
| TypeArguments | 4120 | 0.54% |
| DispatchTable | 3200 | 0.42% |
| OneByteString | 2889 | 0.38% |
| TypeParameter | 2337 | 0.31% |
| Field | 1437 | 0.19% |
| Script | 1148 | 0.15% |
| ArtificialRoot | 1116 | 0.15% |
| CodeSourceMap | 1093 | 0.14% |
| PatchClass | 828 | 0.11% |
| CompressedStackMaps | 784 | 0.10% |
| (RO) PcDescriptors | 672 | 0.09% |
| TypedData | 515 | 0.07% |
| Library | 400 | 0.05% |
| ClosureData | 372 | 0.05% |
| SignatureData | 242 | 0.03% |
| ExceptionHandlers | 180 | 0.02% |
| GrowableObjectArray | 143 | 0.02% |
| Closure | 136 | 0.02% |
| Instance | 61 | 0.01% |
| UnlinkedCall | 56 | 0.01% |
| (RO) InstructionsSection | 48 | 0.01% |
| SubtypeTestCache | 34 | 0.00% |
| Image | 32 | 0.00% |
| MegamorphicCache | 24 | 0.00% |
| PcDescriptors | 18 | 0.00% |
| TypeRef | 4 | 0.00% |
| double | 2 | 0.00% |
| ArgumentsDescriptor | 0 | 0.00% |
| Bytecode | 0 | 0.00% |
| ContextScope | 0 | 0.00% |
| bool | 0 | 0.00% |
| LocalVarDescriptors | 0 | 0.00% |
| Null | 0 | 0.00% |
+--------------------------+--------------+---------+
Total: 764496 bytes
--------------------------------------------------------------------------------
IMPORTANT: Dart AOT snapshot is a serialized representation of Dart VM heap.
Outside of few specific cases (e.g. an object representing a library clearly
originates from the library it represents) there is no well defined relationship
between snapshot bytes and a specific method/class/library to which these
bytes can be attributed with certainty. This snapshot analysis tool tries
to attribute bytes to specific program structure elements based on their
reachability from objects with well defined origin - meaning that this analysis
has some margin of error and imprecision.
- @other bucket denotes bytes attributed to entities outside of the current
granularity. For example, when breaking down the size by method name there
might be bytes which exist outside of any specific symbol - in which case
they will be attributed to @other.
- @stubs bucket accumulates bytes attributed to stubs (pieces of machine code
produced by the VM for internal purposes).
- @shared bucket accumulates bytes shared between otherwise unrelated program
entities
- @unknown bucket accumulates bytes which are not reachable from any program
structure nodes (usually VM internal objects).
--------------------------------------------------------------------------------
Note how this output also includes information about which types of objects are there in the snapshot.
We can look at the size of a specific item by using --where
flag.
$ pkg/vm/bin/snapshot_analysis.dart summary -b class -w dart:core /tmp/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% |
| dart:core | _IntegerImplementation | 8953 | 3.19% | 1.17% |
| dart:core | _OneByteString | 7718 | 2.75% | 1.01% |
| dart:core | _GrowableList | 6976 | 2.49% | 0.91% |
| dart:core | _InvocationMirror | 6772 | 2.41% | 0.89% |
| dart:core | _Smi | 5536 | 1.97% | 0.72% |
| dart:core | StringBuffer | 5529 | 1.97% | 0.72% |
| dart:core | _SimpleUri | 5098 | 1.82% | 0.67% |
| dart:core | _Double | 4850 | 1.73% | 0.63% |
| dart:core | UriData | 4631 | 1.65% | 0.61% |
| dart:core | _List | 4303 | 1.53% | 0.56% |
| dart:core | Object | 3641 | 1.30% | 0.48% |
| dart:core | FormatException | 3567 | 1.27% | 0.47% |
| dart:core | List | 3394 | 1.21% | 0.44% |
| dart:core | _Closure | 3117 | 1.11% | 0.41% |
| dart:core | Duration | 3028 | 1.08% | 0.40% |
| dart:core | _ImmutableMap | 2878 | 1.03% | 0.38% |
| dart:core | _RegExp | 2675 | 0.95% | 0.35% |
| dart:core | RangeError | 2633 | 0.94% | 0.34% |
| dart:core | ArgumentError | 2492 | 0.89% | 0.33% |
| dart:core | _AssertionError | 2352 | 0.84% | 0.31% |
| dart:core | Iterable | 2209 | 0.79% | 0.29% |
| dart:core | _ImmutableList | 1697 | 0.61% | 0.22% |
| dart:core | _RegExpMatch | 1675 | 0.60% | 0.22% |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44 more rows accounting for 19841 (2.60% of total) bytes
on average that is 451 (0.16%) bytes per row
+-----------+------------------------+--------------+---------+----------+
In visible rows: 260578 (34.08% of total)
Total: 764496 bytes
Breakdown by object type:
+--------------------------+--------------+---------+----------+
| Type | Size (Bytes) | Percent | Of total |
+--------------------------+--------------+---------+----------+
| (RO) Instructions | 172712 | 61.59% | 22.59% |
| (RO) _OneByteString | 35536 | 12.67% | 4.65% |
| (RO) CodeSourceMap | 24880 | 8.87% | 3.25% |
| (RO) CompressedStackMaps | 13888 | 4.95% | 1.82% |
| Function | 10051 | 3.58% | 1.31% |
| Code | 7947 | 2.83% | 1.04% |
| Array | 5399 | 1.93% | 0.71% |
| Class | 3828 | 1.37% | 0.50% |
| int | 1657 | 0.59% | 0.22% |
| Type | 1505 | 0.54% | 0.20% |
| OneByteString | 877 | 0.31% | 0.11% |
| TypeArguments | 524 | 0.19% | 0.07% |
| CodeSourceMap | 373 | 0.13% | 0.05% |
| CompressedStackMaps | 284 | 0.10% | 0.04% |
| Field | 281 | 0.10% | 0.04% |
| TypeParameter | 279 | 0.10% | 0.04% |
| PatchClass | 192 | 0.07% | 0.03% |
| ClosureData | 87 | 0.03% | 0.01% |
| (RO) PcDescriptors | 32 | 0.01% | 0.00% |
| SignatureData | 19 | 0.01% | 0.00% |
| Closure | 16 | 0.01% | 0.00% |
| ExceptionHandlers | 10 | 0.00% | 0.00% |
| TypedData | 8 | 0.00% | 0.00% |
| UnlinkedCall | 8 | 0.00% | 0.00% |
| MegamorphicCache | 8 | 0.00% | 0.00% |
| Instance | 6 | 0.00% | 0.00% |
| SubtypeTestCache | 5 | 0.00% | 0.00% |
| TypeRef | 4 | 0.00% | 0.00% |
| double | 2 | 0.00% | 0.00% |
| PcDescriptors | 1 | 0.00% | 0.00% |
| ArgumentsDescriptor | 0 | 0.00% | 0.00% |
+--------------------------+--------------+---------+----------+
In visible rows: 280419 (36.68% of total)
Total: 764496 bytes
$ pkg/vm/bin/snapshot_analysis.dart summary -b method -w dart:core::_Uri /tmp/profile.json
+-----------+------------------------------------------+--------------+---------+----------+
| Library | Symbol | Size (Bytes) | Percent | Of total |
+-----------+------------------------------------------+--------------+---------+----------+
| dart:core | _Uri._normalizeRegName | 3765 | 8.64% | 0.49% |
| @other | | 3732 | 8.57% | 0.49% |
| dart:core | _Uri._normalizeZoneID | 3000 | 6.89% | 0.39% |
| dart:core | _Uri._makeWindowsFileUrl | 2510 | 5.76% | 0.33% |
| dart:core | _Uri.resolveUri | 2406 | 5.52% | 0.31% |
| dart:core | _Uri._normalize | 2219 | 5.09% | 0.29% |
| dart:core | _Uri._makeHost | 2097 | 4.81% | 0.27% |
| dart:core | _Uri._uriEncode | 1917 | 4.40% | 0.25% |
| dart:core | _Uri._normalizeRelativePath | 1780 | 4.09% | 0.23% |
| dart:core | _Uri._mergePaths | 1550 | 3.56% | 0.20% |
| dart:core | _Uri.new _Uri.notSimple | 1308 | 3.00% | 0.17% |
| dart:core | _Uri.== | 1226 | 2.81% | 0.16% |
| dart:core | _Uri._escapeChar | 1203 | 2.76% | 0.16% |
| dart:core | _Uri.new _Uri. | 1179 | 2.71% | 0.15% |
| dart:core | _Uri._removeDotSegments | 1147 | 2.63% | 0.15% |
| dart:core | _Uri._makeScheme | 1095 | 2.51% | 0.14% |
| dart:core | _Uri._checkWindowsPathReservedCharacters | 1024 | 2.35% | 0.13% |
| dart:core | _Uri._normalizeEscape | 1018 | 2.34% | 0.13% |
| dart:core | _Uri._escapeScheme | 979 | 2.25% | 0.13% |
| dart:core | _Uri._makePath | 752 | 1.73% | 0.10% |
| dart:core | _Uri._initializeText | 558 | 1.28% | 0.07% |
| dart:core | _Uri._checkWindowsDriveLetter | 455 | 1.04% | 0.06% |
| dart:core | _Uri._makeFileUri | 430 | 0.99% | 0.06% |
| dart:core | _Uri._checkZoneID | 382 | 0.88% | 0.05% |
| dart:core | _Uri._canonicalizeScheme | 367 | 0.84% | 0.05% |
| dart:core | _Uri._makeQuery | 338 | 0.78% | 0.04% |
| dart:core | _Uri._normalizeOrSubstring | 338 | 0.78% | 0.04% |
| dart:core | _Uri._makeFragment | 334 | 0.77% | 0.04% |
| dart:core | _Uri._writeAuthority | 330 | 0.76% | 0.04% |
| dart:core | _Uri._makeUserInfo | 326 | 0.75% | 0.04% |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27 more rows accounting for 3798 (0.50% of total) bytes
on average that is 141 (0.32%) bytes per row
+-----------+------------------------------------------+--------------+---------+----------+
In visible rows: 39765 (5.20% of total)
Total: 764496 bytes
Breakdown by object type:
+--------------------------+--------------+---------+----------+
| Type | Size (Bytes) | Percent | Of total |
+--------------------------+--------------+---------+----------+
| (RO) Instructions | 30152 | 69.21% | 3.94% |
| (RO) CodeSourceMap | 4576 | 10.50% | 0.60% |
| (RO) _OneByteString | 3712 | 8.52% | 0.49% |
| (RO) CompressedStackMaps | 2640 | 6.06% | 0.35% |
| Function | 1129 | 2.59% | 0.15% |
| Code | 906 | 2.08% | 0.12% |
| Array | 206 | 0.47% | 0.03% |
| OneByteString | 87 | 0.20% | 0.01% |
| Field | 60 | 0.14% | 0.01% |
| CodeSourceMap | 47 | 0.11% | 0.01% |
| CompressedStackMaps | 43 | 0.10% | 0.01% |
| ClosureData | 5 | 0.01% | 0.00% |
+--------------------------+--------------+---------+----------+
In visible rows: 43563 (5.70% of total)
Total: 764496 bytes
We can also edit our hello.dart
to import more from dart:io
and see how this affects the size of the snapshot
import 'dart:io';
void main(List<String> args) {
print(File(Platform.script.toFilePath()).readAsStringSync());
}
env DART_CONFIGURATION=ProductX64 pkg/vm/tool/precompiler2 -v -Ddart.vm.product=true --write-v8-snapshot-profile-to=/tmp/profile2.json /tmp/hello.dart /tmp/snapshot.elf
pkg/vm/bin/snapshot_analysis.dart compare -b package /tmp/profile.json /tmp/profile2.json
+------------------------+--------------+---------+
| Package | Diff (Bytes) | Percent |
+------------------------+--------------+---------+
| dart:io | 24555 | 47.63% |
| dart:core | 22775 | 44.18% |
| dart:async | 1526 | 2.96% |
| @unknown | 1101 | 2.14% |
| dart:_internal | 745 | 1.45% |
| @shared | 436 | 0.85% |
| dart:typed_data | 286 | 0.55% |
| file:///tmp/hello.dart | 263 | 0.51% |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| dart:wasm | -33 | -0.06% |
| dart:_builtin | -33 | -0.06% |
| dart:isolate | -69 | -0.13% |
+------------------------+--------------+---------+
Total: 51552 bytes
Comparing /tmp/profile.json (old) to /tmp/profile2.json (new)
Old : 764496 bytes.
New : 816048 bytes.
Change: +51552 bytes.
$ pkg/vm/bin/snapshot_analysis.dart compare -b class /tmp/profile.json /tmp/profile2.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% |
| dart:io | _FileResourceInfo | 3809 | 7.39% |
| dart:core | Stopwatch | 2736 | 5.31% |
| dart:io | _File | 2432 | 4.72% |
| dart:io | _CopyingBytesBuilder | 2415 | 4.68% |
| dart:io | _IOResourceInfo | 2251 | 4.37% |
| dart:io | _ReadWriteResourceInfo | 1666 | 3.23% |
| dart:io | _BytesBuilder | 1617 | 3.14% |
| dart:io | _Platform | 1439 | 2.79% |
| dart:io | _RandomAccessFileOpsImpl | 1131 | 2.19% |
| dart:async | _Future | 1078 | 2.09% |
| dart:core | Uri | 804 | 1.56% |
| dart:io | BytesBuilder | 507 | 0.98% |
| dart:async | Future | 425 | 0.82% |
| dart:core | DateTime | 408 | 0.79% |
| dart:_internal | :: | 392 | 0.76% |
| dart:_internal | VMLibraryHooks | 373 | 0.72% |
| dart:typed_data | Uint8List | 298 | 0.58% |
| file:///tmp/hello.dart | :: | 263 | 0.51% |
| dart:core | List | 226 | 0.44% |
| dart:io | _RandomAccessFileOps | 165 | 0.32% |
| dart:core | _GrowableList | 132 | 0.26% |
| dart:io | Platform | 102 | 0.20% |
| dart:core | :: | 27 | 0.05% |
| dart:async | :: | 17 | 0.03% |
| dart:io | _ExternalBuffer | 2 | 0.00% |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10 more rows accounting for -178 (-0.35% of total) bytes
on average that is -18 (-0.03%) bytes per row
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| dart:isolate | _RawReceivePortImpl | -33 | -0.06% |
| dart:core | _LateInitializationError | -33 | -0.06% |
| dart:_builtin | :: | -33 | -0.06% |
| dart:core | UriData | -33 | -0.06% |
| dart:io | InternetAddressType | -33 | -0.06% |
| dart:isolate | _Timer | -36 | -0.07% |
| dart:io | _SocketProfile | -41 | -0.08% |
| dart:io | _IOService | -49 | -0.10% |
| dart:core | _IntegerImplementation | -50 | -0.10% |
| dart:core | _StringMatch | -72 | -0.14% |
+------------------------+--------------------------+--------------+---------+
In visible rows: 51730 (100.35% of total)
Total: 51552 bytes
Comparing /tmp/profile.json (old) to /tmp/profile2.json (new)
Old : 764496 bytes.
New : 816048 bytes.
Change: +51552 bytes.