Loop Printing

The task of sending a string to a file or the display is an I/O-bound task. Using multithreaded techniques on a loop of output does not make sense. Since the operation is I/O-bound, the threads will spend most of their time waiting, and there is little difference in having one processor or twelve processors available to run waiting threads. Furthermore, the order of the output is important. Data that is written to a file or the display will eventually be read by a person or another application. The output must look the same whether the calculation is done as a single-threaded or multithreaded application.

However, what if the printing portion of the loop is small when compared with the mathematical calculation? If enough of the loop is CPU intensive, it might be silly to abandon an attempt at parallelizing the loop just because it contains a println() method call. The only problem that needs to be solved is the ordering of the output. This can be done by a two-step printing process. Instead of printing directly to the display or file, the application can print to a virtual, memory-based display along with an index that is used to order the output. When the processing of the loop has completed, the output can then be sent to the display or file, using the index information to ensure that the data is sent in the correct order.

Let’s reexamine our SinTable loop:

public synchronized float[] getValues() {
    if (lookupValues == null) {
        for (int i = 0; i < (360*100); i++) {
            float sinValue = (float)Math.sin((i % 360)*Math.PI/180.0);
            lookupValues[i] = sinValue * (float)i / 180.0f;
            System.out.println(" " + i + "    " + lookupValues[i]);
        }
    }  
    return lookupValues;
}

In this new version of the getValues() method, we are also printing the table to standard output. Obviously, this is a simple example that can be transformed with a loop distribution to two separate loops. But let us assume that the printing process is highly integrated into the algorithm and the loop transformation is not possible.

To solve this problem, we’ll use this class:

import java.util.*;
import java.io.*;

public class LoopPrinter {
    private Vector pStorage[];
    private int growSize;

    public LoopPrinter(int initSize, int growSize) {
        pStorage = new Vector[initSize];
        this.growSize = growSize;
    }

    public LoopPrinter() {
        this(100, 0);
    }

    private synchronized void enlargeStorage(int minSize) {
        int oldSize = pStorage.length;
        if (oldSize < minSize) {
            int newSize = (growSize > 0) ?
                oldSize + growSize : 2 * oldSize;
            if (newSize < minSize) {
                newSize = minSize;
            }    
            Vector newVec[] = new Vector[newSize];
            System.arraycopy(pStorage, 0, newVec, 0, oldSize);
            pStorage = newVec;
        }
    }
 
    public synchronized void print(int index, Object obj) {
        if (index >= pStorage.length) {
            enlargeStorage(index+1);
        }
        if (pStorage[index] == null) {
            pStorage[index] = new Vector();
        }
        pStorage[index].addElement(obj.toString());
    }
 
    public synchronized void println(int index, Object obj) {
        print(index, obj);
        print(index, "
");
    }
 
    public synchronized void send2stream(PrintStream ps) {
        for (int i = 0; i < pStorage.length; i++) {
            if (pStorage[i] != null) {
                Enumeration e = pStorage[i].elements();
                while (e.hasMoreElements()) {
                    ps.print(e.nextElement());
                }
            }    
        }
    }
}

Implementation of a loop printer is done with a two-dimensional vector. The first dimension is used to separate the output. This output index could be related to the index of the actual loop, or to a chunk of the loop, or it could even be a combination of multiple loop indices. In any case, an output index should not be assigned to more than one thread, since the ordering inside an indexed vector is based on it. The second dimension holds the strings that will be sent to the output. Since the indices have already ordered the strings to be printed, this dimension is just used to store the many strings that will be sent to this index.[13]

Printing an object to the virtual display is done with the print() and println() methods. Along with the object to be printed, the application must supply an index as a reference of the printing order. These methods simply store a reference to the strings so that they may be printed at a later time. The second phase of the printing process is done by the send2stream() method. Once the loop has completed, a call to this method will print the result to the output specified.

Here’s how to use the LoopPrinter class:

public class SinTable extends GuidedLoopHandler {
    private float lookupValues[];
    private LoopPrinter lp;

    public SinTable() {
        super(0, 360*100, 100, 12);
        lookupValues = new float [360 * 100];
        lp = new LoopPrinter(360*100, 0);
    }

    public void loopDoRange(int start, int end) {
        for (int i = start; i < end; i++) {
            float sinValue = (float)Math.sin((i % 360)*Math.PI/180.0);
            lookupValues[i] = sinValue * (float)i / 180.0f;
            lp.println(i, " " + i + " " + lookupValues[i]);
        }
    }    

    public float[] getValues() {
        loopProcess();
        lp.send2stream(System.out);
        return lookupValues;
    }
}

The loop printer is created prior to the loop, all printing that was previously sent to a file or the display is sent to the loop printer, and the send2stream() method is called upon completion of the loop. Since the loop printer will send all the information to one target, multiple loop printers will have to be created if the loop prints to different streams.

Also note that we constructed the loop printer with the index size as its initial size. The loop printer is written to expand to any size, so this extra definition is not necessary. We want to avoid expanding the size because this operation not only requires the method to be synchronized, but also, depending on the size, will take some time to execute. The print() and println() methods must also be synchronized. This serves two purposes: First, it allows the array size to be increased without a race condition. Second, it allows the methods to work—although the print order is no longer guaranteed—if an index is assigned to two threads. If the loop printer were modified so as not to allow the array to be enlarged, and if it were assumed that developers would not assign two threads to the same index, synchronization at this level would no longer be necessary.



[13] Technically, we could have done the same thing with a single-dimensional array of string buffers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset