Nightly rustc provides the print-type-sizes
option for printing sizes of all types. It's especially useful for analyzing why futures are soo big, that can hurt performance a lot if such futures are moved.
However, print-type-sizes
produces unordered and cluttered output. This crate parses that output, refine it and show top types by size in compact form.
Features:
- Sorts types by size.
- Deduplicates same types.
- Merges variants with similar layout.
- Shows layouts in compact form.
- Sorts fields by size (
-s
). - Hides small types and fields (
-h
). - Hides wrappers like
MaybeUninit
and custom ones (-w
). - Filters by type names (
-f
and-e
). - Limits output (
-l
). - Expands specific types with children (
-p
).
Firstly, install by using cargo install top-type-sizes
or clone the repository and run cargo build --release
.
Secondly, compile your project:
$ RUSTFLAGS=-Zprint-type-sizes cargo +nightly build -j 1 > type-sizes.txt
- It should be a fresh build without cache. Otherwise, part of info will be lost.
-Zprint-type-sizes
requires the nightly compiler.-j 1
is required to avoid incorrect shuffled output.
Finally, use this crate:
$ top-type-sizes < type-sizes.txt | less
$ top-type-sizes --help
top-type-sizes 0.2.0
USAGE:
top-type-sizes [FLAGS] [OPTIONS]
FLAGS:
--expand-by-size
Modify the -p/--expand option to expand also by field's size
--help
Prints help information
-w, --remove-wrappers
Hides wrappers like `MaybeUninit` and `ManuallyDrop`.
This option removes types having the same layout as an inner type.
-r, --reverse
Prints types in descending order.
This option is applied after the -l/--limit option.
-s, --sort-fields
Sorts fields by size and hides paddings.
Note: enum variants are sorted and merged anyway.
-V, --version
Prints version information
OPTIONS:
-e, --exclude <exclude>...
Excludes types that match these patterns.
Patterns are regex (in the regex crate's syntax). Can be provided multiple times.
-p, --expand <expand>...
Shows only types that match these patterns and their children.
It uses two mechanisms to expand types:
- by field's type name (requires at least nightly 24-03-22)
- by field's size if the `--expand_by_size` option is enabled
Note: currently field's type names are provided only for `await`.
Patterns are regex (in the regex crate's syntax). Can be provided multiple times.
-f, --filter <filter>...
Shows only types that match these patterns.
Patterns are regex (in the regex crate's syntax). Can be provided multiple times.
-h, --hide-less <hide-less>
Hides types and fields with size less than this value
-l, --limit <limit>
Shows only this number of top types.
This limit is applied after all other filters.
For instance, let's analyze the tokio/chat
example:
RUSTFLAGS=-Zprint-type-sizes cargo +nightly build --example chat -j 1 > chat.txt
Once the compiler's output is collected, we can perform multiple queries until results become representative.
Initially, show all types sorted by size and find interesting ones:
top-type-sizes < chat.txt | less
For instance, if we want to expand async fn process()
function:
...
1024 {async fn body of process()} align=8
...
We can use the -p
/--expand
option to show only this function and its children types:
top-type-sizes -ws -h33 -p 'process\(\)' < chat.txt | less
-w
hides wrappers, e.g.1032 std::mem::MaybeUninit<[async fn body@examples/chat.rs:174:33: 243:2]> align=8 1032 variant MaybeUninit 1032 value
-s
sorts fields by size and hides paddings.-h <size>
hides all types and fields with size less than the provided size.-p <pattern>
hides all types that aren't contained in<patten>
types. Note that the compiler doesn't provide types of fields for all types, only for awaitees. It's possible to use--expand-by-size
to expand also by field's size, but it can show also irrelevant types.
Output:
1024 {async fn body of process()} align=8
1023 variant Suspend2
472 __awaitee align=8 type={async fn body of Peer::new()}
144 lines
40 stream (upvar) align=8 offset=0
663 variant Suspend3, Suspend7, Suspend9
152 peer
144 lines
112 __awaitee align=8 type={async fn body of tokio::sync::Mutex<Shared>::lock()}
40 stream (upvar) align=8 offset=0
639 variant Suspend4, Suspend8, Suspend10
152 peer
144 lines
64 __awaitee type={async fn body of Shared::broadcast()}
40 stream (upvar) align=8 offset=0
615 variant Suspend5
152 peer
144 lines
40 stream (upvar) align=8 offset=0
40 futures align=8
591 variant Suspend6
152 peer
144 lines
40 stream (upvar) align=8 offset=0
575 variant Suspend0
144 lines
40 stream (upvar) align=8 offset=0
559 variant Suspend1
144 lines
40 stream (upvar) align=8 offset=0
80 variant Unresumed, Returned, Panicked
40 stream (upvar) align=8 offset=0
472 {async fn body of Peer::new()} align=8
465 variant Suspend0
144 lines (upvar) align=8 offset=0
144 lines
112 __awaitee type={async fn body of tokio::sync::Mutex<Shared>::lock()}
152 variant Unresumed, Returned, Panicked
144 lines (upvar) align=8 offset=0
112 {async fn body of tokio::sync::Mutex<Shared>::lock()} align=8
104 variant Suspend0
88 __awaitee type={async block@tokio::sync::Mutex<Shared>::lock::{closure#0}::{closure#0}}
8 variant Unresumed, Returned, Panicked
88 {async block@tokio::sync::Mutex<Shared>::lock::{closure#0}::{closure#0}} align=8
80 variant Suspend0
72 __awaitee type={async fn body of tokio::sync::Mutex<Shared>::acquire()}
8 variant Unresumed, Returned, Panicked
72 {async fn body of tokio::sync::Mutex<Shared>::acquire()} align=8
64 variant Suspend0
56 __awaitee type=tokio::sync::batch_semaphore::Acquire<'_>
8 variant Unresumed, Returned, Panicked
64 {async fn body of Shared::broadcast()} align=8
56 variant Unresumed, Returned, Panicked
56 tokio::sync::batch_semaphore::Acquire<'_> align=8
40 node
Note: __awaitee
means awaiting on an inner future.
Then, we can use -f
and -e
to refine output even more.