Break down C++ code size
Asked Answered
S

8

11

I'm looking for a nice Stack Overflow-style answer to the first question in the old blog post C++ Code Size, which I'll repeat below:

I’d really like some tool (ideally, g++ based) that shows me what parts of compiled/linked code are generated from what parts of C++ source code. For instance, to see whether a particular template is being instantiated for hundreds of different types (fixable via a template specialization) or whether code is being inlined excessively, or whether particular functions are larger than expected.

Snack answered 24/3, 2010 at 17:2 Comment(1)
Is a linker map file what you want? Something like g++ -Wl,map,prog.map?Phonic
M
5

It does seem like something like this should exist, but I haven't used anything like it. I can tell you how I'd go about scripting this together, though. There are probably swifter and/or sexier ways to do it.

First some stuff that you may already know:

The addr2line command takes in an address and can tell you where the source code that the machine code there implements. The executable needs to be built with debugging symbols, and you'll probably not want to optimize it much (-O0, -O1, or -Os is probably as high as you'd want to go at first anyway). addr2line has several flags, and you'll want to read its manual page, but you will definitely need to use -C or --demangle if you want to see C++ function names that make sense in the output.

The objdump command can print out all kinds of interesting things about the stuff in many types of object files. One of the things it can do is print out a table representing the symbols in or referred to by an object file (including executables).

Now, what you want to do with that:

What you'll want to is for objdump to tell you the address and size of the .text section. This is where actual executable machine code lives. There are several ways to do this, but the easiest (for this, anyway) is probably for you to do:

objdump -h my_exe | grep text

That should result in something like:

 12  .text       0000049  000000f000  0000000f000 00000400  2**4

If you didn't grep it it would give you a heading like:

Idx  Name        Size     VMA         LMA         File off  Algn

I think for executables the VMA and LMA should be the same, so it won't matter which you use, but I think LMA is the best. You'll also want the size.

With the LMA and size you can repeatedly call addr2line asking for the source code origin of the machine code. I'm not sure how this would work if you passed an address that was within one instruction, but I think it should work.

addr2line -e my_exe <address>

The output from this will be a path/filename, a colon, and a line number. If you were to count the occurrence of each unique path/file:num you should be able to look at the ones that have the highest counts. Perl hashes using the path/file:num as the key and a counter as the value would be an easy way to implement this, though there are faster ways if you find that runs too slow. You could also filter out things that you can determine don't need to be included early. For displaying your output you may want to filter out different lines from the same function, but you may notice that different lines within one function have different counts, which could be interesting. Anyway, that could be done either by making addr2line tell you the function name or using objdump -t in the first step and work one function at a time.

If you see that some template code or other code lines are showing up in your executables more often than you think they should then you can easily locate them and have a closer look. Macros and inline functions may show end up manifesting themselves differently than you expect.

If you didn't know, objdump and addr2line are from the GNU binutils package, which includes several other useful tools.

Mariner answered 24/3, 2010 at 19:41 Comment(0)
U
10

If you're looking to find sources of code bloat in your C++ code, I've used 'nm' for that. The following command will list all the symbols in your app with the biggest code and data chunks at the top:

nm --demangle --print-size --size-sort --reverse-sort <executable_or_lib_name> | less
Unman answered 24/3, 2010 at 21:34 Comment(0)
M
5

It does seem like something like this should exist, but I haven't used anything like it. I can tell you how I'd go about scripting this together, though. There are probably swifter and/or sexier ways to do it.

First some stuff that you may already know:

The addr2line command takes in an address and can tell you where the source code that the machine code there implements. The executable needs to be built with debugging symbols, and you'll probably not want to optimize it much (-O0, -O1, or -Os is probably as high as you'd want to go at first anyway). addr2line has several flags, and you'll want to read its manual page, but you will definitely need to use -C or --demangle if you want to see C++ function names that make sense in the output.

The objdump command can print out all kinds of interesting things about the stuff in many types of object files. One of the things it can do is print out a table representing the symbols in or referred to by an object file (including executables).

Now, what you want to do with that:

What you'll want to is for objdump to tell you the address and size of the .text section. This is where actual executable machine code lives. There are several ways to do this, but the easiest (for this, anyway) is probably for you to do:

objdump -h my_exe | grep text

That should result in something like:

 12  .text       0000049  000000f000  0000000f000 00000400  2**4

If you didn't grep it it would give you a heading like:

Idx  Name        Size     VMA         LMA         File off  Algn

I think for executables the VMA and LMA should be the same, so it won't matter which you use, but I think LMA is the best. You'll also want the size.

With the LMA and size you can repeatedly call addr2line asking for the source code origin of the machine code. I'm not sure how this would work if you passed an address that was within one instruction, but I think it should work.

addr2line -e my_exe <address>

The output from this will be a path/filename, a colon, and a line number. If you were to count the occurrence of each unique path/file:num you should be able to look at the ones that have the highest counts. Perl hashes using the path/file:num as the key and a counter as the value would be an easy way to implement this, though there are faster ways if you find that runs too slow. You could also filter out things that you can determine don't need to be included early. For displaying your output you may want to filter out different lines from the same function, but you may notice that different lines within one function have different counts, which could be interesting. Anyway, that could be done either by making addr2line tell you the function name or using objdump -t in the first step and work one function at a time.

If you see that some template code or other code lines are showing up in your executables more often than you think they should then you can easily locate them and have a closer look. Macros and inline functions may show end up manifesting themselves differently than you expect.

If you didn't know, objdump and addr2line are from the GNU binutils package, which includes several other useful tools.

Mariner answered 24/3, 2010 at 19:41 Comment(0)
R
4

I recently wrote a tool, bloat-blame, which does something similar to what nategoose proposed.

Reiter answered 3/2, 2012 at 14:43 Comment(0)
T
2

In most C compilers there is a way to generate a .map file. This file lists all of the compiled libraries their address and their size. You can use that map file to help you determine which files you should be looking to optimize first.

Top answered 26/3, 2010 at 13:2 Comment(0)
I
2

You can check out bloaty for analyzing the binary size of your program:

https://github.com/google/bloaty

./bloaty bloaty -d compileunits
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  34.8%  10.2Mi  43.4%  2.91Mi    [163 Others]
  17.2%  5.08Mi   4.3%   295Ki    third_party/protobuf/src/google/protobuf/descriptor.cc
   7.3%  2.14Mi   2.6%   179Ki    third_party/protobuf/src/google/protobuf/descriptor.pb.cc
   4.6%  1.36Mi   1.1%  78.4Ki    third_party/protobuf/src/google/protobuf/text_format.cc
   3.7%  1.10Mi   4.5%   311Ki    third_party/capstone/arch/ARM/ARMDisassembler.c
   1.3%   399Ki  15.9%  1.07Mi    third_party/capstone/arch/M68K/M68KDisassembler.c
   3.2%   980Ki   1.1%  75.3Ki    third_party/protobuf/src/google/protobuf/generated_message_reflection.cc
   3.2%   965Ki   0.6%  40.7Ki    third_party/protobuf/src/google/protobuf/descriptor_database.cc
   2.8%   854Ki  12.0%   819Ki    third_party/capstone/arch/X86/X86Mapping.c
   2.8%   846Ki   1.0%  66.4Ki    third_party/protobuf/src/google/protobuf/extension_set.cc
   2.7%   800Ki   0.6%  41.2Ki    third_party/protobuf/src/google/protobuf/generated_message_util.cc
   2.3%   709Ki   0.7%  50.7Ki    third_party/protobuf/src/google/protobuf/wire_format.cc
   2.1%   637Ki   1.7%   117Ki    third_party/demumble/third_party/libcxxabi/cxa_demangle.cpp
   1.8%   549Ki   1.7%   114Ki    src/bloaty.cc
   1.7%   503Ki   0.7%  48.1Ki    third_party/protobuf/src/google/protobuf/repeated_field.cc
   1.6%   469Ki   6.2%   427Ki    third_party/capstone/arch/X86/X86DisassemblerDecoder.c
   1.4%   434Ki   0.2%  15.9Ki    third_party/protobuf/src/google/protobuf/message.cc
   1.4%   422Ki   0.3%  23.4Ki    third_party/re2/re2/dfa.cc
   1.3%   407Ki   0.4%  24.9Ki    third_party/re2/re2/regexp.cc
   1.3%   407Ki   0.4%  29.9Ki    third_party/protobuf/src/google/protobuf/map_field.cc
   1.3%   397Ki   0.4%  24.8Ki    third_party/re2/re2/re2.cc
 100.0%  29.5Mi 100.0%  6.69Mi    TOTAL
Inunction answered 18/7, 2021 at 23:49 Comment(0)
A
1

I don't know if it will help but there is a gcc flag to write the assembly code it generates to a text file for your examination.

"-S Used in place of -c to cause the assembler source file to be generated, using .s as the extension, instead of the object file. This may be useful if you need to examine the generated assembly code. "

Airline answered 24/3, 2010 at 22:14 Comment(1)
Thanks, that is useful but I was hoping for something more tailored to my problem.Snack
T
0

I don't know how to map code->generated assembly in general.

For template instantiations you can use something like "strings -a |grep |sort -u|gc++filt" to get a rough picture of what's being created.

The other two items you mentioned seem pretty subjective actually. What is "too much" inlining? Are you worried your binary file is getting inflated? The only thing to do there is actually go into gdb and disassemble the caller to see what it generated, nothing to check for "excessive" inlining in general.

For function size, again I'm curious why it matters? Are you trying to find code that expands unexpectedly when compiled? How do you even define what an expected size is for a tool to examine? Again, you can always dissemble any function that you suspect is compiling to far more code than you want, and see exactly what the compiler is doing.

Transpontine answered 24/3, 2010 at 18:39 Comment(1)
In regards to "why it matters?" we are developing on a platform with a fixed limit for code size. Some insight would help us find the problem areas to attack first.Snack
D
0

In Visual C++, this is essentially what .PDB files are for.

Defeatism answered 24/3, 2010 at 22:22 Comment(1)
Can you provide details? How can I determine the code-size associated with a symbol?Snack

© 2022 - 2024 — McMap. All rights reserved.