Command to debug binary size

I came up with this command to help me figure out what was taking up so much space in one of my binaries:

nm -S target/release/my-binary \
  | awk '{
           print $4 |& "rustfilt";
           "rustfilt" |& getline id;
           sub(/::h[0-9a-f]{16}$/, "", id);
           sums[id] += strtonum("0x"$2);
           counts[id] += 1
         }
         END{
           for (id in sums) {
             printf "%8s %4s  %s\n", sums[id], counts[id], id
           }
         }' \
  | sort -nr \
  | head -16

I have only used it with GNU awk and GNU nm so if you are using BSD stuff it may not work, but hopefully this is useful to people.

The first output column gives the total size in bytes across all instantiations of that function, and the second column gives the number of instantiations (one for each combination of generic parameters).

Output looks like:

  174550   42  <core::marker::PhantomData<T> as serde::de::DeserializeSeed>::deserialize
   53537    4  <serde_json::de::Deserializer<R>>::parse_value
   29883   61  <serde_json::de::Deserializer<R>>::parse_integer
   19929   61  <serde_json::de::Deserializer<R>>::parse_exponent
   18492   61  <serde_json::de::Deserializer<R>>::visit_f64_from_parts
   16438   61  <serde_json::de::Deserializer<R>>::parse_decimal
   12567    1  <json_benchmark::serde_json::Library as json_benchmark::Library>::parse_struct
   12425    1  <json_benchmark::serde_json::Library as json_benchmark::Library>::stringify_struct
   10693    1  json_benchmark::num_trials
   10084   61  <serde_json::de::Deserializer<R>>::parse_exponent_overflow
   10076   52  <serde_json::de::Deserializer<R>>::parse_number
    9570    1  stats_arena_print
    8628   21  <serde_json::ser::Compound<'a, W, F> as serde::ser::SerializeStruct>::serialize_field
    7831    1  <&'a mut serde_json::de::Deserializer<R> as serde::de::Deserializer>::deserialize
    7562   45  drop
    6522    1  std::sys_common::backtrace::output

This gave me everything I needed for now but I would also love to hear if anyone has other approaches for this.

4 Likes

I'm getting "rustfilt: command not found" with gnu awk and syntax error at source line 2 on nawk

Try cargo install rustfilt, or build it yourself from here.

1 Like

that helped, many thanks!

I put together a python script which works on .map files (regardless of
language). It prints the size that each object file contributes. Sometimes,
the big items are data rather than functions.
https://github.com/fabricedesre/cc3200-rs/

Here's some sample output from an embedded rust program:

50472 libfreertos_alloc.rlib(heap_4.o)
10691 libcc3200.rlib(cc3200.0.o)
7510 libcore.rlib(core.0.o)
4410 fill
3420 temperature.0.o
3021 liblog-bf16bb9a4912b11d.rlib(log-bf16bb9a4912b11d.0.o)
2760 libgcc.a(unwind-arm.o)
2664 libfreertos_rs-be066203913bbcb3.rlib(freertos_rs-be066203913bbcb3.0.o)
2025 libcc3200_sys.rlib(tasks.o)
986 libcc3200_sys.rlib(prcm.o)
972 libgcc.a(pr-support.o)

That example had a 50,000 byte heap.

and similar output from a purely C project:

19032 fill
18977 qstr.o
14951 compile.o
11716 frozen_mpy.o
10835 ff.o
9931 objstr.o
9387 emitnthumb.o

The fill in that example happens to be mostly stack space.

Bah - my link was wrong. Correct link is:
https://github.com/fabricedesre/cc3200-rs/blob/master/scripts/map.py