aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2023-08-19 00:33:57 +0200
committerJohannes Stoelp <johannes.stoelp@gmail.com>2023-08-22 23:37:33 +0200
commitfbbae01e8ccd29821a6b476cccb10220a7ed21ab (patch)
treec59dffab4d78768e545e5a37ae47752903b17cca
parente7b2cecc90ef105091de249698d8c61e50a8764b (diff)
downloadnotes-fbbae01e8ccd29821a6b476cccb10220a7ed21ab.tar.gz
notes-fbbae01e8ccd29821a6b476cccb10220a7ed21ab.zip
add pgo notes
-rw-r--r--src/SUMMARY.md1
-rw-r--r--src/development/README.md1
-rw-r--r--src/development/pgo.md158
3 files changed, 160 insertions, 0 deletions
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index fb22e69..b6da0b7 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -50,6 +50,7 @@
- [symbol versioning](./development/symbolver.md)
- [python](./development/python.md)
- [gcov](./development/gcov.md)
+ - [pgo](./development/pgo.md)
- [Linux](./linux/README.md)
- [systemd](./linux/systemd.md)
diff --git a/src/development/README.md b/src/development/README.md
index dfea5cd..117ef17 100644
--- a/src/development/README.md
+++ b/src/development/README.md
@@ -9,3 +9,4 @@
- [symbol versioning](./symbolver.md)
- [python](./python.md)
- [gcov](./gcov.md)
+- [pgo](./pgo.md)
diff --git a/src/development/pgo.md b/src/development/pgo.md
new file mode 100644
index 0000000..d1b3eb7
--- /dev/null
+++ b/src/development/pgo.md
@@ -0,0 +1,158 @@
+# Profile guided optimization (pgo)
+
+`pgo` is an optimization technique to optimize a program for its usual
+workload.
+
+It is applied in two phases:
+1. Collect profiling data (best with representative benchmarks).
+1. Optimize program based on collected profiling data.
+
+The following simple program is used as demonstrator.
+```c
+#include <stdio.h>
+
+#define NOINLINE __attribute__((noinline))
+
+NOINLINE void foo() { puts("foo()"); }
+NOINLINE void bar() { puts("bar()"); }
+
+int main(int argc, char *argv[]) {
+ if (argc == 2) {
+ foo();
+ } else {
+ bar();
+ }
+}
+```
+
+## clang
+
+On the actual machine with `clang 15.0.7`, the following code is generated for
+the `main()` function.
+```x86asm
+# clang -o test test.c -O3
+
+0000000000001160 <main>:
+ 1160: 50 push rax
+ ; Jump if argc != 2.
+ 1161: 83 ff 02 cmp edi,0x2
+ 1164: 75 09 jne 116f <main+0xf>
+ ; foor() is on the hot path (fall-through).
+ 1166: e8 d5 ff ff ff call 1140 <_Z3foov>
+ 116b: 31 c0 xor eax,eax
+ 116d: 59 pop rcx
+ 116e: c3 ret
+ ; bar() is on the cold path (branch).
+ 116f: e8 dc ff ff ff call 1150 <_Z3barv>
+ 1174: 31 c0 xor eax,eax
+ 1176: 59 pop rcx
+ 1177: c3 ret
+```
+
+The following shows how to compile with profiling instrumentation and how to
+optimize the final program with the collected profiling data ([llvm
+pgo][llvm-pgo]).
+
+The arguments to `./test` are chosen such that `9/10` runs call `bar()`, which
+is currently on the `cold path`.
+
+```bash
+# Compile test program with profiling instrumentation.
+clang -o test test.cc -O3 -fprofile-instr-generate
+
+# Collect profiling data from multiple runs.
+for i in {0..10}; do
+ LLVM_PROFILE_FILE="prof.clang/%p.profraw" ./test $(seq 0 $i)
+done
+
+# Merge raw profiling data into single profile data.
+llvm-profdata merge -o pgo.profdata prof.clang/*.profraw
+
+# Optimize test program with profiling data.
+clang -o test test.cc -O3 -fprofile-use=pgo.profdata
+```
+> NOTE: If `LLVM_PROFILE_FILE` is not given the profile data is written to
+> `default.profraw` which is re-written on each run. If the `LLVM_PROFILE_FILE`
+> contains a `%m` in the filename, a unique integer will be generated and
+> consecutive runs will update the same generated profraw file,
+> `LLVM_PROFILE_FILE` can specify a new file every time, however that requires
+> more storage in general.
+
+After optimizing the program with the profiling data, the `main()` function
+looks as follows.
+```x86asm
+0000000000001060 <main>:
+ 1060: 50 push rax
+ ; Jump if argc == 2.
+ 1061: 83 ff 02 cmp edi,0x2
+ 1064: 74 09 je 106f <main+0xf>
+ ; bar() is on the hot path (fall-through).
+ 1066: e8 e5 ff ff ff call 1050 <_Z3barv>
+ 106b: 31 c0 xor eax,eax
+ 106d: 59 pop rcx
+ 106e: c3 ret
+ ; foo() is on the cold path (branch).
+ 106f: e8 cc ff ff ff call 1040 <_Z3foov>
+ 1074: 31 c0 xor eax,eax
+ 1076: 59 pop rcx
+ 1077: c3 ret
+```
+
+## gcc
+
+With `gcc 13.2.1` on the current machine, the optimizer puts `bar()` on the
+`hot path` by default.
+```x86asm
+0000000000001040 <main>:
+ 1040: 48 83 ec 08 sub rsp,0x8
+ ; Jump if argc == 2.
+ 1044: 83 ff 02 cmp edi,0x2
+ 1047: 74 0c je 1055 <main+0x15>
+ ; bar () is on the hot path (fall-through).
+ 1049: e8 22 01 00 00 call 1170 <_Z3barv>
+ 104e: 31 c0 xor eax,eax
+ 1050: 48 83 c4 08 add rsp,0x8
+ 1054: c3 ret
+ ; foo() is on the cold path (branch).
+ 1055: e8 06 01 00 00 call 1160 <_Z3foov>
+ 105a: eb f2 jmp 104e <main+0xe>
+ 105c: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
+
+```
+
+The following shows how to compile with profiling instrumentation and how to
+optimize the final program with the collected profiling data.
+
+The arguments to `./test` are chosen such that `2/3` runs call `foo()`, which
+is currently on the `cold path`.
+
+```bash
+gcc -o test test.cc -O3 -fprofile-generate
+./test 1
+./test 1
+./test 2 2
+gcc -o test test.cc -O3 -fprofile-use
+```
+> NOTE: Consecutive runs update the generated `test.gcda` profile data file
+> rather than re-write it.
+
+After optimizing the program with the profiling data, the `main()` function
+```x86asm
+0000000000001040 <main.cold>:
+ ; bar() is on the cold path (branch).
+ 1040: e8 05 00 00 00 call 104a <_Z3barv>
+ 1045: e9 25 00 00 00 jmp 106f <main+0xf>
+
+0000000000001060 <main>:
+ 1060: 51 push rcx
+ ; Jump if argc != 2.
+ 1061: 83 ff 02 cmp edi,0x2
+ 1064: 0f 85 d6 ff ff ff jne 1040 <main.cold>
+ ; for() is on the hot path (fall-through).
+ 106a: e8 11 01 00 00 call 1180 <_Z3foov>
+ 106f: 31 c0 xor eax,eax
+ 1071: 5a pop rdx
+ 1072: c3 ret
+```
+
+[llvm-pgo]: https://clang.llvm.org/docs/UsersManual.html#profile-guided-optimization