CSE271 Lab 6: Debugging and Profiling Tools

Announcements

Today: More Tools for Debugging and Optimization

Static Code Checkers

lint -- a static code checker

Splint -- Secure Programming Lint

Using splint

Let's use splint on an example. Recall the troublesome leak.c program that we used last week to demonstrate dynamic memory debugging. Copy it to your directory. Run splint over it: You will see a number of warnings about real problems in the code (memory leaks, unreachable code, problems with free(), dereferencing of possibly bad pointers, etc.). You will also see some warnings about things that might be changed to improve security (adding the static keyword). In this example, splint is able to find most (if not all) of the errors; in more complex programs the dynamic memory checking packages are needed.

System call tracers

Strace

truss

  • The truss(1) utility in Solaris is roughly equivalent to strace --- it executes the specified command and produces a trace of the system calls it performs, the signals it receives, and the machine faults it incurs. Each line of the trace output reports either the fault or signal name or the system call name with its arguments and return value(s). System call arguments are displayed symbolically when possible using defines from relevant system headers; for any path name pointer argument, the pointed-to string is displayed.

    dtrace

    Using a system call tracer

    Let's see truss in action. At a system prompt, type:
         % truss pwd
      
    This will generate a list of all the system calls made by the pwd (print working directory) command. Each one of these should be documented in the system man pages, so you can get a very good feel for what is happening, but it is not important right now to understand the calls. pwd is a very simple command, and only generates about 30 calls. For a somewhat more complex one, try running truss on df, or better yet, on emacs:
         % truss -f -o truss.out emacs
      
    Rather than printing the calls to the screen, truss writes its output to a file so that emacs can handle the screen. If you just start and then stop emacs within a shell, you'll get more than 1200 system calls. Looking through traces like this, you'll find what libraries are needed by a program and where it looks for them, as well as where emacs stores temporary files.

    Code Optimization

    Optimizing Code

    Code Profilers

    gprof

    Using a profiler

    Now let's try it out ourselves.
         % gcc -pg -g -o leak leak.c
         % leak
         % gprof leak
      
    This very simple program has very little to report via the profiler. It is able to show how often each routine is called, but since the program executed so quickly, it could not show any significant cpu usage for any part of the program. In longer running, more complex programs, you will see which routines used what fraction of your cpu time (and how often they were executed), allowing you to focus your efforts on improving those routines needing the most help.

    Rules for writing fast code (aka optimization)

    More from the Slashdot discussion...
    1. Avoid doing what you don't have to do. Sounds obvious but I rarely see code that does the absolute minimum it needs to. Most of the code I've seen to date seems to precalculate too much stuff, read too much data from external storage, redraw too much stuff on screen etc...
    2. Do it later. There are thousands of situations where you can postpone the actual computations.
      • Most string class implementations already make good use of this rule by only copying their buffers only when the "copied" buffer changes. Operating systems also use the "copy-on-write" rule when copying memory pages that can otherwise be shared (e.g., when fork()ing a process).
    3. Apply minimum algorithmic complexity. If you can use a hashmap instead of a treemap use the hash version it's O(1) vs O(log n). Use quicksort for just about any kind of sorting you need to do.
    4. Cache your data. There are some enormous performance gains that can be realized with smart caching strategies.
    5. That's it! If you are applying rules one to four you can have fast AND readable code.

    Last revised: 25 February 2007, Prof. Davison.