Week 05


Software Development Process1/59

Reminder of how software development runs ...

Typically, iterate over the implementation/testing phases.

Credits:


... Software Development Process2/59

Tools available to assist each stage ...

For testing, assert and (even) printf are also useful.


Testing


Testing4/59

A systematic process of determining whether a program

Testing requires:


... Testing5/59

Testing happens at different stages in development:

A useful approach for unit testing:


... Testing6/59

Testing alone cannot establish that a program is correct.

Why not?

To show that a program is correct, we need to show

Problem: even small programs have too many possible inputs.

Testing increases our confidence that the program is ok.

Well-chosen tests significantly increase our confidence.


Exercise: Testing a function7/59

Consider the notion that an array int A[N] is ordered:

ordered(A[N]):  forall i:1..N-1,  A[i] >= A[i-1]

and some code to implement this test

ordered = 1; // assume that it *is* ordered
for (i = 1; i < N; i++) {
    if (A[i] >= A[i-1])
        /* ok ... still ordered */;
    else
        ordered = 0; // found counter-example
}
// ordered is set appropriately

Put this code in a function and write a driver to test it.

Print "Yes" if ordered, or print "No" otherwise.


Testing Strategy8/59

Testing only increases our confidence that the program works.

Unfortunately, we tend to make the big assumption:

"It works for my test data, so it'll work for all data"

The only way to be absolutely sure that this is true:

Exhaustive testing is not possible in practice ...


... Testing Strategy9/59

Realistic testing:

If, for each input, the program gives the expected output, then


Developing Test Cases10/59

Examples:

Kind of
Problem
Partitions of input
values for testing
Numeric +value, -value, zero
Text 0, 1, 2, many text elements;
lines of zero and huge length
List 0, 1, 2, many list items
Ordering ordered, reverse ordered
random order, duplicates
Sorting same as for ordering


... Developing Test Cases11/59

Years of (painful) experience have yielded some common bugs:

Choose test cases to ensure that all such bugs will be exercised.

Requires you to understand the "limit points" of your program.


Making Programs Fail12/59

Some techniques that you can use to exercise potential bugs:


Summary: Testing Strategies13/59


Debugging


Debugging15/59

Debugging = process of removing errors from software.

Required when observed output != expected output.

Typically ...

Bug: code fragment that does not satisfy its specification.


... Debugging16/59

Consequences of bugs:


** but if the runtime error is due to pointer mismanagement, you're very unlucky


... Debugging17/59

Debugging has three aspects:

Generally ...

To fix: re-examine spec, modify code to satisfy spec.


... Debugging18/59

The easiest bugs to find:

The most difficult bugs to find: Assumptions are what makes debugging difficult.

Corollary: an onlooker will find the bug quicker than you.


Debugging Strategies19/59

The following will assist in the task of finding bugs:


Debuggers20/59

Debuggers: tools to assist in finding bugs

Typically provide facilities to

Examples:


Exercise: Buggy Program21/59

Spot the bugs in the following simple program:

int main(int argc, char *argv[]) {
	int i, sum;
	int a[] = {7, 4, 3};

	for (i = 1; i <= 3; i++) {
		sum += a[i];
	}
	printf(sum);
	return EXIT_SUCCESS;
}

What will we observe when it's compiled/executed?


The Debugging Process


The Debugging Process23/59

Debugging requires a detailed understanding of program state.

The state of a program comprises:

Simple example, considering just local vars in a function: Note: for any realistic program, the state will be rather large ...


... The Debugging Process24/59

The real difficulty of debugging is locating the bug.

Since a bug is "code with unintended action", you need to know:

In any non-trivial program, the sheer amount of detail is a problem.

The trick to effective debugging is narrowing the focus of attention.

That is, employ a search strategy that enables you to zoom in on the bug.


... The Debugging Process25/59

When you run a buggy program, initially everything is ok.

At some point, the buggy statement is executed ...

The goal: find the point at which the state becomes "corrupted"

Typically, you need to determine which variables "got the wrong value".


Locating the Bug26/59

A simple search strategy for debugging is as follows:


... Locating the Bug27/59

At each stage you have eliminated half of the program from suspicion.

In not too many steps, you will have identified a specific buggy statement.

The problems:

Side note: this approach won't necessarily find an existing bug ...

E.g.     x:int { y = x+x; } y==x2,   when intially   x==2


... Locating the Bug28/59

A slightly smarter strategy, relying on the typical structure of programs:

How to determine strategic points? E.g.


Examining Program State29/59

A vital tool for debugging is a mechanism to display state.

One method: diagnostic printf statements of "suspect" variables.

Problems with this approach:


... Examining Program State30/59

An alternative for obtaining access to program state:

This is precisely what debuggers such as gdb provide.

Debuggers also allow you to inspect the state of a program that has crashed due to a run-time error.

Often, this takes you straight to the point where the bug occurred.


C Program Execution31/59

Under Unix, a C program executes either:

Normal C execution environment:

[Diagram:Pic/unixproc-small.png]


... C Program Execution32/59

C execution environment with a debugger:

 

[Diagram:Pic/gdbproc-small.png]


Debuggers33/59

A debugger gives control of program execution:

gdb command is a command-line-based debugger for C, C++ ...

There are GUI front-ends available (e.g. xxgdb, ddd, ...).


... Debuggers34/59

For gdb, programs must be compiled with the gcc -g flag.

gdb takes two command line arguments:

$ gdb executable core

E.g.

$ gdb  a.out  core
$ gdb  myprog

The core argument is optional.


gdb Sessions35/59

gdb is like a shell to control and monitor an executing C program.

Example session:

$ gcc -g -Wall -Werror -o prog prog.c
$ gdb prog
Copyright (C) 2014 Free Software Foundation, Inc...
(gdb) break 9
Breakpoint 1 at 0x100f03: file prog.c, line 9.
(gdb) run
/Users/comp1921 Starting program: ..../prog 

Breakpoint 1, main (argc=1, argv=0x7ffbc8) at prog.c:9
9               for (i = 1; i <= 3; i++ { 
(gdb) next
10              sum += a[i];
(gdb) print sum
$1 = 0
(gdb) print a[i]
$2 = 4
(gdb) print i
$3 = 1
(gdb) print a@1
$4 = {{7, 4, 3}}
(gdb) cont
...


Basic gdb Commands36/59

quit -- quits from gdb

help [CMD] -- on-line help (gives information about CMD command)

run ARGS -- run the program


gdb Status Commands37/59

where -- find which function the program was executing when it crashed.

list [LINE] -- display five lines either side of current statement.

print EXPR -- display expression values


gdb Execution Commands38/59

break [PROC|LINE] -- set break-point

On entry to procedure PROC (or reaching line LINE), stop execution and return control to gdb.

next -- single step (over procedures)

Execute next statement; if statement is a procedure call, execute entire procedure body.

step -- single step (into procedures)

Execute next statement; if statement is a procedure call, go to first statement in procedure body.

For more details see gdb's on-line help.


Using a Debugger39/59

The most common time to invoke a debugger is after a run-time error.

If this produces a core file, start gdb ...

Note, however, that the program may crash well after the bug ...


... Using a Debugger40/59

Once you have an idea where the bug might be:

This will eventually reveal a variable which has an incorrect value.


... Using a Debugger41/59

Once you find that the value of a given variable (e.g. x) is wrong, the next step is to determine why it is wrong.

There are two possibilities:

Example:

if (c > 0) { x = a+b; }

If we know that

Then we need to find out where a, b and c were set.


Laws of Debugging42/59

Courtesy of Zoltan Somogyi, Melbourne University

Before you can fix it, you must be able to break it (consistently).
(non-reproducible bugs ... Heisenbugs ... are extremely difficult to deal with)

If you can't find a bug where you're looking, you're looking in the wrong place.
(taking a break and resuming the debugging task later is generally a good idea)

It takes two people to find a subtle bug, but only one of them needs to know the program.
(the second person simply asks questions to challenge the debugger's assumptions)

(In fact, sometimes the second person doesn't have to do or say anything! The process of explaining the problem is often enough to trigger a Eureka event.)


Possibly Untrue Assumptions43/59

Debugging can be extremely frustrating when you make assumptions about the problem which turn out to be wrong.

Some things to be wary of:


Performance Tuning


Software Development Process45/59

Reminder of how software development runs ...

Typically, iterate over the implementation/testing phases.


Performance46/59

Why do we care about performance?

Good performance less hardware, happy users.

Bad performance more hardware, unhappy users.

Generally:   performance = execution time

Other measures: memory/disk space, network traffic, disk i/o.

Execution time can be measured in two ways:


... Performance47/59

In the past, performance was a significant problem.

Unfortunately, there is usually a trade-off between ...


Knuth: "Premature optimization is the root of all evil"


Development Strategy48/59

A pragmatic approach to efficiency:

Points to note: Pike: "A fast program that gets the wrong answer saves no time."


... Development Strategy49/59

Strategy for developing efficient programs:

  1. Design the program well
  2. Implement the program well
  3. Test the program well
  4. Only after you're sure it's working, measure performance
  5. If (and only if) performance is inadequate, find the "hot spots"
  6. Tune the code to fix these
  7. Repeat measure-analyse-tune cycle until performance ok


Performance Analysis50/59

Complexity/estimates give some idea of performance in advance.

Often, however ..

Best way to evaluate performance: measure program execution.

Performance analysis can be:


... Performance Analysis51/59

Coarse-grained performance analysis

The Unix time command provides a suitable mechanism

$ time ./myProg < LargeInput > /dev/null
real  0m5.064s
user  0m4.113s
sys   0m0.802s


... Performance Analysis52/59

Decades of empirical study of program execution have shown ...

The 90/10 rule generally holds   (or 80/20 rule or ...):

``90% of the execution time is spent in 10% of the code''

Implications:

To significantly improve performance: make bottlenecks faster.


Profiles53/59

Need a method for locating hot spots (the expensive 10% of code).

An execution profile for a program is

Cost may be measured via Profiles typically collected at function level   (i.e. code block = function).


... Profiles54/59

A profile shows how much time is spent in each code block ...

[Diagram:Pic/tools/profgraph-small.png]

Software tools can generate profiles of program execution.


gprof: A Profiler55/59

The gprof command displays execution profiles ...

Example of use:

$ gcc -pg -o xyz xyz.c
$ xyz < data > /dev/null
$ gprof xyz | less

For further usage details, man gprof.


... gprof: A Profiler56/59

The gprof command works at the function level.

It gives a table (flat profile) containing:

Arranged in order from most expensive function down.

It also gives a call graph, a list for each function:


Profile Example57/59

Consider the following program that

int main(int argc, char*argv[])
{
    char  word[MAXWORD];  // current word
    List  matches;        // list of matched words
    char  *substring;     // string to look for
    FILE  *input;         // the input file

    /* ... Check command-line args, open input file ... */

    /* Process the file - find the matching words */
    matches = NULL;
    while (getWord(input, word) != NULL) {
        if (contains(word,substring)
                 && !member(matches,word))
            matches = insert(matches,word);
    }
    printWords(matches);
    return 0;
}


... Profile Example58/59

Flat profile for this program (xwords et data3):


  %  cumulative     self            self    total           
 time   seconds  seconds  calls  us/call  us/call  name    
 75.00     0.03     0.03  30212     0.99     0.99  getWord
 25.00     0.04     0.01  30211     0.33     0.33  contains
  0.00     0.04     0.00    489     0.00     0.00  member
  0.00     0.04     0.00    267     0.00     0.00  insert
  0.00     0.04     0.00      1     0.00 40000.00  main
  0.00     0.04     0.00      1     0.00     0.00  printWords

Note:   wc data3    7439 30211 188259.


... Profile Example59/59

Call graph for the same execution (xwords et data3):


index %time   self  children    called     name
              0.00    0.04       1/1           _start [2]
[1]   100.0   0.00    0.04       1         main [1]
              0.03    0.00   30212/30212       getWord [3]
              0.01    0.00   30211/30211       contains [4]
              0.00    0.00     489/489         member [5]
              0.00    0.00     267/267         insert [6]
              0.00    0.00       1/1           printWords [7]
-----------------------------------------------
[2]   100.0   0.00    0.04                 _start [2]
              0.00    0.04       1/1           main [1]
-----------------------------------------------
              0.03    0.00   30212/30212       main [1]
[3]    75.0   0.03    0.00   30212         getWord [3]
-----------------------------------------------
              0.01    0.00   30211/30211       main [1]
[4]    25.0   0.01    0.00   30211         contains [4]
----------------------------------------------
              0.00    0.00     489/489         main [1]
[5]     0.0   0.00    0.00     489         member [5]
-----------------------------------------------
              0.00    0.00     267/267         main [1]
[6]     0.0   0.00    0.00     267         insert [6]
-----------------------------------------------
              0.00    0.00       1/1           main [1]
[7]     0.0   0.00    0.00       1         printWords [7]


Produced: 21 Aug 2017