
This folder is part of the C Programming Tools lectures.

This is an example on how to apply debugging and profiling tools to a simple
hash table module.

  (C) Duncan C. White, 2017
      Evangelos Vervaras, 2017
      Pedro A.M. Mediano, 2015


  Finding memory leaks
  ==================================================================

  - Run iterate and monitor with time and htop. Study how time and memory
    change with the number of iterations (memory should be constant, time
    should be linear).

  - Unexpected non-linear behaviour for high N! The program starts eating
    your whole RAM, and it might even start swapping. There are memory leaks.
    (Note: non-linear behaviour appears earlier in older machines, like
    coronas or rays)

  - Look for memory leaks with

    valgrind ./iterate

  - valgrind confirms that some blocks are never freed, and suggests that
    you run it again with more detail:

    valgrind --leak-check=full ./iterate

    do that.  aha.  now valgrind suggests 4 linenos in hash.c to go look, where
    something was malloc()d.  Check: for each of those things, were they ever
    freed?  go look.  think what might be happening.
    (Hint: look for something missing in hashFree)
  
  - Now go and fix it. Don't leave that chair until valgrind says there are
    no memory leaks.

  - You can measure the performance of the program using time and plot it
    using gnuplot. You will need to have gnuplot installed to visualise
    (available in the lab machines).

    $ ./testme
    $ gnuplot plotscript.dem


  Profiling and optimizing
  ==================================================================

  - Now the hash table doesn't leak, but it has never been optimized.
    Let's use gprof to see where the bottlenecks are and work on them.

  - Start by adding the '-pg' flag to the CC variable in the Makefile,
    to make sure everything is compiled and linked with profiling symbols.
    Make the program via

	     make clean all

    to force a total rebuild, then run "./iterate" again, and you should see
    a gmon.out file.

  - The gmon.out file contains the flat profile and the call graph of your
    program, in a binary format. You can see it by typing

    $ gprof ./iterate

    And gprof automatically looks for matching symbols in gmon.out. If you
    want to supress the explanations:

    $ gprof -b --flat-profile ./iterate

  - It's a fairly suspicious number of calls to copy_tree and free_tree.
    Specially taking into account the value of NHASH. Perhaps the program
    is trying to copy and free things it shouldn't be?

  - In the test program, most trees are empty, but hashFree, hashCopy and
    hashForeach are calling their helper functions once per tree. Those
    helpers immediately return if the tree is empty.  So: how long do
    hundreds of millions of unnecessary function calls take?

  - Figure out a way to eliminate those unnecessary calls in hashFree,
    hashCopy and hashForeach. Profile and time again to measure your progress.

  - And now the program is much faster! A quick, simple optimization we
    spotted by using a profiler. You can get a good estimator of the time
    per iteration by looking at the slope of the line fitted by gnuplot.


  Extra
  ==================================================================
  
  - Further profiling-led optimizations might include reducing the
    number of trees (NHASH) to some smaller prime number, investigating
    how well the keys are spread among the trees.  Reduce NHASH too far
    and hash keys may start to cluster in particular trees which become
    too deep and unbalanced.
  
  - Is a tree rebalancer worth writing?  If so, when it is worth invoking?
    Similarly, is a dynamic array resize and all-key rehash ever worthwhile?

  - Note that several of these investigations probably require storing a more
    realistic number of (key,value) pairs in the various hash tables.  Can
    we obtain a realistic (key,value) pair dataset from somewhere?

  - Let me know if you come up with any cool stuff!

