Show Lecture.Inlining as a slide show.
CS253 Inlining
Split Interface and Implementation
- We generally split a non-trivial class into two parts,
interface (in the
.h
file)
and implementation (in the .cc
) file.
- This way, a user, who only wants to know what the class does,
can look at the
.h
file and see the interface.
- The implementation details, how the class does what it does,
are of little interest to the user.
- The implementation code is typically much larger than the interface
(one line per method) and so can make the interface hard to find.
- Also, the implementation may be a trade secret. We may wish to hide
our source code from our business competitors, and only sell the
.h
and .o
(or library .a
) files.
The Problem
- Students often worry too much about optimization.
- Students guess, without data, that the overhead of method calls is
slowing down their code, as opposed to their O(n³) algorithm.
- Therefore, the students foolishly inline their method
bodies, putting everything in the
.h
file.
- This makes reading and maintaining the class quite difficult.
- Do not optimize for speed or space unless you need to.
- “May I see your profiling data, please?” —Bret McKee, HP
Simple Class
Numbers.h
:
class Numbers {
public:
static int two(), three();
};
Numbers.cc
:
#include "Numbers.h"
int Numbers::two() {
return 2;
}
int Numbers::three() {
return 3;
}
main.cc
:
#include "Numbers.h"
int main() {
return Numbers::two() + Numbers::three();
}
Compile with no optimization
% cp ~cs253/Example/Inline/* .
% g++ -Wall -c main.cc
% g++ -Wall -c Numbers.cc
% g++ -Wall main.o Numbers.o
% objdump --no-show-raw-insn --demangle --disassemble | sed '/<main>/,/^$/!d'
0000000000400556 <main>:
400556: push %rbp
400557: mov %rsp,%rbp
40055a: push %rbx
40055b: sub $0x8,%rsp
40055f: callq 400574 <Numbers::two()>
400564: mov %eax,%ebx
400566: callq 400580 <Numbers::three()>
40056b: add %ebx,%eax
40056d: add $0x8,%rsp
400571: pop %rbx
400572: pop %rbp
400573: retq
- main() called
Numbers::two()
.
- This is not surprising. That’s what we told it to do.
Optimization
There are several optimization arguments to g++:
-O
— general optimization
-O1
— same as -O
-O2
— more optimization
-O3
— tons of optimization
Compile with -O3
optimization
% cp ~cs253/Example/Inline/* .
% g++ -Wall -O3 -c main.cc
% g++ -Wall -O3 -c Numbers.cc
% g++ -Wall -O3 main.o Numbers.o
% objdump --no-show-raw-insn --demangle --disassemble | sed '/<main>/,/^$/!d'
0000000000400470 <main>:
400470: push %rbx
400471: callq 400580 <Numbers::two()>
400476: mov %eax,%ebx
400478: callq 400590 <Numbers::three()>
40047d: add %ebx,%eax
40047f: pop %rbx
400480: retq
- Instead of main() calling
Numbers::two()
, and then returning,
main() simply transferred control to Numbers::two()
.
- This is called tail call optimization.
- This is better, but still not optimal. Ideally, if an
omniscient being were compiling our code, then
main() would simply return the value
2
.
Link-Time Optimization
-flto
— Perform link-time optimization
This tells the linker to perform optimization between
the individual *.o
object files.
Link-time optimization
% cp ~cs253/Example/Inline/* .
% g++ -Wall -O3 -flto -c main.cc
% g++ -Wall -O3 -flto -c Numbers.cc
% g++ -Wall -O3 -flto main.o Numbers.o
% objdump --no-show-raw-insn --demangle --disassemble | sed '/<main>/,/^$/!d'
0000000000400470 <main>:
400470: mov $0x5,%eax
400475: retq
Holy smokes! 😲 The code from Numbers.cc
,
which was in a completely separate file, got integrated into main()!
Conclusion
- Don’t write methods in the header file.
- Put them in the
*.cc
file instead.
- Interface goes into
*.h
.
- Implementation goes into
*.cc
.
- If speed matters, use
g++ -O3 -flto
.
- I will occasionally write inline methods, for compact examples.
Sorry!