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
main.cc
:
#include "Numbers.h"
int main() {
return Numbers::two() + Numbers::three();
}
Numbers.h
:
class Numbers {
public:
static int two(), three();
};
Numbers.cc
:
#include "Numbers.h"
int Numbers::two() {
return 2;
}
int Numbers::three() {
return 3;
}
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()
& Numbers::two()
,
and added the results. Sure.
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
A bit better, but not much. Really, we can’t expect more. How is it
supposed to know, when compiling main.cc
, what the functions
two()
and three()
do?
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(), and the
addition got done at compile time! You could not produce smaller or
faster code if you wrote the assembly language yourself! Nice work,
compiler!
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!