Parallel Knapsack

To illustrate the MPI_Iprobe and the termination problem, we looked at the knapsack problem. Our version of this problem has the following specifications:

Input: n number of objects;
w array of n weights, w(i) is weight of i-th item;
m maximal number of items allowed to choose;
L lower bound on the sum of the weights;
U upper bound on the sum of the weights.
Output: all I: I is subset of { 1,2,..,n }, #I ≤ m
and the sum of w(i), for i in I lies between L and U.

Because of its combinatorial nature, this problem becomes quickly intractable, even already for relatively small values of n, and thus it is a very good problem for supercomputing. We implement a solution using plain backtracking, in a generic version which allows us to use the same recursive function either to interactively list the solutions, or just to count them.

Our formulation of this knapsack problems is general enough to illustrate both the termination problem and the need for dynamic load balancing. On the one hand, when L is very close to U, we may find only few solutions and in the extreme case none at all. If we are interested in knowing only one solution, then all processors must terminate their search as soon as one processor finds a solution. On the other hand, when L and U are very far apart, we may find many solutions, and then the problem might be to evaluate the fitness of the selected items by some computationally intensive procedure. If the number of items determines the cost of the evaluation, we have many jobs of varying size.

We first illustrate the natural static load balancing, on eight processors. Because we distribute the search space before the computations, the natural and easy way is to start the 8 searches at the fourth position, initializing the choice vector with the binary decomposition of the label of every processor. The output of 4 consecutive runs shows the great variation in the output and demonstrates the need for dynamic load balancing if the number of selections determines the computational cost on each node.

To solve the termination problem, we extended the generic function with two extra parameters: the label of the processor and the total number of processors. We instantiate the search procedure with a terminate function. The processor which finds a solution prints the solutions and sends a message to every other processor. The content of the message is the label of the terminating processor. Every processor polls for incoming messages in a nonblocking way, using MPI_Iprobe. Once there is an incoming message, the processor acknowledges the receipt of the message printing the content of the message, and then terminates. A typical output of the simple program poll_knap.c shows how this works.

Bibliography