Introduction

This lab will give you experience using both CPU and memory profilers.

You will need to create a file named answers.md to write responses in.

Connecting to the right machines

For this lab, you will need to use a machine that has gcc version 4.8.41. The physical Linux hosts (the ones in CS 213) seem to have this version installed (mostly).

You can check whether a box has the right version installed by running the following after you’ve logged in:

$ gcc --version
gcc (...blah blah blah...) 4.8.4

Make sure that you tell PuTTY to connect to one of the physical hosts. The hosts will have names with a U in them: rc__ucs213.managed.mst.edu, not rc__xcs213.managed.mst.edu.

Problem 0: Clean up after yourself

You know the drill.

Create a .gitignore file and add stuff you want to ignore.

You will lose points…

  1. If you don’t have a .gitignore.
  2. If you commit compiled files, editor droppings, or other junk.

Problem 1: massif

Remember ye olde filter.cpp? It’s back again!

  1. Build prob1.cpp and run it through massif

    # I'm assuming the compiled program is named `prob1`.
    # The < operator is used to send data from a file to a program's
    # standard input pipe (STDIN).
    valgrind --tool=massif --time-unit=B ./prob1 < story.txt
    
    1. What is the peak memory (total(B)) allocation?
    2. Paste the graph in your answers.md file, so that it is a properly formatted ASCII graph.
      • Use a verbatim block (~~~).
      • You can use the commonmark.js dingus to format your graph before you paste it in your answers.md.
      • After you push to GitLab, make sure that your plot looks OK when you pull up your answers.md.
  2. Open up prob1.cpp.
    1. Do you really need to store every line of the input if you’re just printing it out?
    2. Fix the code, so that you only keep the current line of input.
  3. Run the fixed code through massif again.
    1. What is the peak memory allocation this time?

Problem 2: gprof

This problem’s code appends random numbers (between 1 and 1000) to a vector and prints the average of the vector as it goes. Hopefully, the average will converge to around 500.

  1. Build prob2.cpp for gprof and run it, then look at gprof’s output.
    1. Which function consumes the most overall time?
      • In other words, which one is ranked number one?
    2. How many times was it called?
  2. Look at the average function.
    1. Why is the vector’s copy constructor being called?
    2. Fix the code to avoid making unnecessary calls to the copy constructor.
      • This doesn’t require a written response. Just fix the code.
  3. Run the code through gprof again.
    1. How has the number of copy constructor calls been affected?
    2. Why does your fix work?

Hints:

  • gprof --brief gives you the brief version. It’s a little easier on the eyes.
  • Use the Flat Profile to identify which function your program is spending so much time on.
  • Use the Call Graph to figure out why we’re calling that function so much.

Problem 3: callgrind

This problem’s code calculates the length of input lines and prints a running average.

  1. Build prob3.cpp and run it through callgrind (it needs input as with Prob 1.1). Callgrind will generate an output file with a name like callgrind.out.NNNN. Run callgrind_annotate, and pass it the file like so:

    callgrind_annotate --auto=yes callgrind.out.NNNN
    
    1. What lines in main() execute a lot of instructions?
      • Let’s call 20,000 lines “a lot”.
      • Be sure to specify the line, as well as the function call (if there is one) that’s taking so many. For example:

        300           for(unsigned int i = 0; i < lines.length(); i++)
        50,000  => /home/bob/lab10/vector.h:Vector<char*>::length() const (65x)
        

        We could say “The for-loop on line 21 only consumes 300 instructions, but the call to Vector::length() on that line consumes 50,000.”

    2. How many instructions are spent calculating the average? (lines 17-27)
  2. Look at the source code.
    1. Do we really need to re-count the length of each line every single time we calculate the average?
      • Hint: no.
    2. Fix the code to maintain a running total of characters and a line count. (This is not a one-line change; please throw away a lot of this slow implementation.)
  3. Re-build and run the code through callgrind.
    1. How many instructions are spent calculating the average now?

Epilogue

Grading

You will be graded on:

  • The layout of your project. You will lose points if…
    • …if files are not named as specified.
    • …files or directories are not present in the expected locations documented at the bottom of this write-up.
  • The presence of a useful .gitignore and absence of junk files.
  • Your answers.md file.
    • Responses must be complete and coherent. Use complete sentences.
    • Make sure you’ve answered all questions in the assignment.
    • Your lines should not exceed 80 columns.
    • Feel free to use Markdown to format your file.
    • Your plot for 1.1.2 must look like a proper massif plot.
  • Your fixed code in prob1.cpp.
    • Use proper C++ style.
    • We will test your code to ensure that it works as expected.
    • Should use significantly less memory than the starter code.
  • Your fixed code in prob2.cpp.
    • Use proper C++ style.
    • We will test your code to ensure that it works as expected.
    • Should make far fewer calls to the function identified in 2.1.1.
  • Your fixed project files in prob3.cpp.
    • Use proper C++ style.
    • We will test your code to ensure that it works as expected.
    • Calculating the average should require a significantly smaller instruction count.

Submissions

As always, your git repo on http://git-classes.mst.edu is your submission. Don’t forget to commit and push all relevant files. Make sure you see everything you expect on GitLab!

We expect to see the following files on your master branch:

  • .gitignore
  • answers.md
  • README.md
  • examples/
    • array.cpp
    • list.cpp
    • Makefile
    • recursive.cpp
    • vector.cpp
  • prob1.cpp
  • prob2.cpp
  • prob3.cpp
  • story.txt
  • vector.h

Notice that your repository has a subdirectory! You have the option to put one .gitignore file at the root of your repo, or individual .gitignore files in each directory if you prefer.

  1. This is in order to avoid that obnoxious leak of 72,704 bytes that we’ve all seen by now. It turns out that GCC version 5.X.X is the root of that leak issue.