CSCI 3907 Assignment #4: Advanced performance profiling with PAPI
AI Policy for this assignment: As you run into errors, I encourage you to copy/paste/google the error messages for AI-based suggestions. I found this approach more helpful than looking at official documentation for PAPI.Part I: Counting FLOPS
In slide #14 in the Jagode/Danalis lecture we covered, there's a nice example of using PAPI to count not just the total compute cycles we got with our in-class PAPI intro, but also the number of FLOPS (Floating-Point Operations Per Second), a crucial benchmark for intensive numerical computations like modern AI / Deep-Learning. In this exercise we will combine both programs to get the FLOPS count. Crucially, we will need to check the result of each PAPI call, as we did in hello-world, to ensure that we don't silently continue to the next step if something doesn't work. To do this, I first wrote a little Check() function that ensures that the result of the PAPI operation equals PAPI_OK. If it isn't, I report the error and exit the program immediately by calling exit(1); (which requires #include <stdlib.h> at the top).Once that was working, I found that there was a problem with PAPI_FP_OPS. A little googling revealed that that feature isn't necessarily supported on every machine/OS, and that I should run the papi_avail command to see what features were avaialable to me. Copy/pasting the following command into your terminal window should help with this:
papi_avail | grep "Yes Yes"Finally, I found it crucial not to use any optimization flags like -O2, or PAPI would mysteriously report zero as the number of FLOPS!
Once you can get a sensible-looking report with a number of FLOPS, copy/paste your do_work() function into your writeup, along with a little comment on whether the number makes sense with respect to what you're doing in the function. (For my function I just did the standard y = ax + b computation inside a for-loop.) Put your code in a file called part1.cpp.
Part II: Creating a helper utility
Looking over the PAPI examples you can find online, you'll see a common pattern:- Call PAPI_library_init() to start PAPI and check that you have the current version.
- Call PAPI_create_eventset() and check the result
- Call PAPI_add_events() and check the result
- Call PAPI_start() and check the result
- Run the code you want to profile
- Call PAPI_stop() to get the event counts
auto papi = PapiUtil();
papi.StartCountingFlops();
// Put your floating-point calculations loop here
printf("FLOPS=%lld\n", papi.GetCount());
Part III: Counting cache misses
As we discussed in class, the reason that the struct-of-arrays approach runs so much faster than array-of-structs approach is that putting data of a common size (int, short, char) in a contiguous block of memory increases the likelihood that the data we want will already be in the cache (ideally, the Level-1 cache L1) when we try to access it. In other words, we'd like to minimize the number of cache misses caused by our code.So, for this exercise, you're going to augument your PapiUtil class by adding a method StartCountingL1Misses(). As usual, copy-paste-modify (from StartCountingFlops(), followed by a little code-factoring, should make it easy to to implement this new method. Next, write a program part3.cpp containing your copy-pasted code from Assignment #3's array_of_structs.cpp and struct_of_arrays.cpp, this time using your PapiUtil class to count L1 cache misses.
Part IV: Counting branch mispredictions
As we also discussed in class, when you write a conditional statement, the compiler generates code trying to predict the likeliest branch ( if, else if ... else) your code will take on execution, so that this likeliest branch can be pre-loaded into the instruction pipeline. Slide #42 of the Jagode/Danalis lecture has a nice example of two pieces of code, the first of which has a conditional based on a nice, regular pattern, and the second of which has a conditional based on a random value.So, for this final exercise, you should add a PapiUtil method StartCountingBranchMispredictions(), and then write a program part4.cpp that uses a modified version of the code in the slide to test your branch-predicting feature. Running papi_avail | grep "Yes Yes" again should reveal what event code to use.
What to submit to github
Because of the amount of coding you needed to do for this assignment, there's no need to do a writeup. Instead, submit the following files:- part1.cpp
- part2.cpp
- part3.cpp
- part4.cpp
- papi_util.hpp
- Makefile
To run all your code, I should be able to type make test, and see something like the following results:./part1 FLOPS=2000000 cycles=8650426 ./part2 FLOPS=2000000 ./part3 SOA caches misses=49397 AOS caches misses=220421 ./part4 Branch mispredictions with non-random = 200010 Branch mispredictions with random = 943296
I may also look at your code to check for DRY violations and the like, so if you have time, you'll want to make it all neat and tidy!