Debugging and Profiling Tools
Prof. Brian D. Davison
Computer Science & Engineering, Lehigh University
Announcements
- New project #4 (debugging) online, due Thursday
- Today's schedule
- Return short quiz #5 -- scores ranged from 2 to 10, mean 6.5
- A few more debugging tools
- Discuss code profiling
More Debugging Tools
- We saw many debugging tools last week
- Trusty printf()
- Debuggers such as gdb + ddd
- Dynamic memory allocation checkers such as ccmalloc, Electric Fence, Purify and Valgrind
- There are a few more to add to your arsenal
- Static code checkers
- System call tracers
lint -- a static code checker
The lint utility attempts to detect features of the named C program
files that are likely to be bugs, to be non-portable, or to be
wasteful. It also performs stricter type checking than the C
compiler.
Lint finds unreachable
statements, loops not entered at the top, variables declared and not
used, logical expressions with constant values,
calls to functions that return
values in some places and not in others, functions called with varying
numbers of arguments, function calls that pass arguments of a type
other than the type the function expects to receive, functions whose
values are not used, and calls to functions not returning values that
use the non-existent return value of the function.
Lint is old (first appeared in 1979). There are commercial
versions, but the modern open-source replacement is Splint. Lint
is not installed on the Suns.
Lint goes further than -Wall, but also complains about non-errors.
Splint -- Secure Programming Lint
- Splint
is a tool for statically checking C programs for security
vulnerabilities and coding mistakes. With minimal effort, Splint can
be used as a better lint. If additional effort is invested adding
annotations to programs, Splint can perform stronger checking than can
be done by any standard lint.
- Versions of Splint are available for Solaris, Linux, and Windows.
- I've installed splint in ~brian/cse271/splint-3.1.1/
- Let's use Splint on some examples.
System call tracers
- Sometimes it is useful to know what system calls a program is making.
- System call tracers like truss and strace can do
so, without re-compiling code.
Strace
-
Strace
is a system call trace, i.e., a debugging tool which prints out a trace
of all the system calls made by a another process/program. The program
to be traced need not be recompiled for this, so you can use it on
binaries for which you don't have source.
-
System calls and signals are events that happen at the user/kernel
interface. A close examination of this boundary is useful for bug
isolation, sanity checking and attempting to capture race conditions.
- Strace is available for a wide variety of platforms.
truss
-
The truss(1) utility in Solaris 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.
- Let's see truss in action.
Code Styles
-
Comments from a recent discussion on Slashdot.
-
"Code can never be 100% self documenting, but that's no reason to settle for 0%. Whether you use CamelCase or words_broken_with_underscores is a matter of style, and you should stick with the style of the code base you're working on.
Anyone who can't or won't work with multiple languages or adopt the necessary style for an existing project is a poor programmer. When you create project, you create the rules. When you work on someone else's project, you follow the rules."
Optimizing Execution Speed
-
Comments from a recent discussion on Slashdot.
-
"The first thing to optimize is the algorithm. Use a O(n^2) algorithm that does the same job as an O(e^n) algorithm if you can. Algorithmical optimization makes the most difference."
- "Write code that is easy to understand and modify, then optimize it, but only after you have profiled it to find out where optimization will actually matter."
- "An important lesson that I wish I had learned when I was younger ;) It is crazy to start optimizing before you know where your bottlenecks are. Don't guess - run a profiler. It's not hard, and you'll likely get some big surprises."
What do the experts say?
-
"Premature optimization is the root of all evil." -- C.A.R. Hoare
-
"We should forget about small efficiencies, say about 97% of the time: Premature optimization is the root of all evil." - Donald Knuth (quoting Hoare)
- Rules of Optimization:
Rule 1: Don't do it.
Rule 2 (for experts only): Don't do it yet.
- M.A. Jackson
- "More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason - including blind stupidity." - W.A. Wulf
Optimizing Code
- Yes, I agree with the experts.
- However, there are times when you do need to
optimize your code
- Code profiling can also sometimes expose bugs when you realize where your CPU time is being spent
- Will also demonstrate why a poor algorithm is indeed poor
Code Profilers
- A code profiler will tell you
- how often a function is called
- which functions used what percentage of your time
- It is then up to you to figure out how to improve upon that behavior
- But now, you know where to focus your efforts
- Examples include gprof, prof, valgrind, etc.
gprof
- gprof is the GNU profiler.
- To use:
- Now let's try it out ourselves.
Rules for writing fast code (aka optimization)
More from the Slashdot discussion...
-
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...
-
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.
-
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.
-
Cache your data.
There are some enormous performance gains that can
be realized with smart caching strategies.
-
Optimize using your language constructs. User the register
keyword, use language idioms that you know compile into faster code
etc... Scratch this rule! If you're applying rules one to four you can
forget about this one and still have fast AND readable code.