Parallel IT · PARCutils

RedLib — Quick Start

Background on the RedLib reduction library of PARCutils, with examples illustrating its features.

1. Framework design and implementation

1.1 Why reductions?

In many parallel computations, each parallel component produces a partial result that must be integrated with the others to compute the final outcome. This integration is called a reduction. As multi-core devices have become ubiquitous, shared-memory parallel computing is predominant, and reductions have long been exploited in parallel systems.

1.2 Design principles of RedLib

The framework emphasises modifiability and code reuse, basing the mechanism on a Reducible class and a base Reduction interface. Every reduction class — whether provided by RedLib or written by a developer — implements the Reduction interface:

public interface Reduction<E> {
    public E reduce(E first, E second);
}

RedLib’s features aim to match the performance of hand-written implementations (no overhead penalties): reusable implementations promoting code reuse; relational algebraic operations (union, intersection of sets) for complex reductions; avoidance of state variables and shared objects so implementations work in parallel; support for nesting reduction objects on dictionary types; and generic <Key, Value> pairs for a wide range of data.

1.3 Categories of implemented reductions

Scalar/simple reductions operate on primitive types: Sum, Multiplication, Average, Minimum, Maximum (Integer, Long, Short, Float, Double, BigInteger, BigDecimal); BitwiseAND/OR/XOR (Boolean, Byte, Integer, Short); AND/OR/XOR (Boolean). Example:

public class FloatAverage implements Reduction<Float> {
    public Float reduce(Float first, Float second) {
        return (first + second) / 2.0f;
    }
}

Collections/aggregate reductions operate on aggregate types using generics — union and intersection of Collections and Sets:

public class SetUnion<T> implements Reduction<Set<T>> {
    public Set<T> reduce(Set<T> first, Set<T> second) {
        for (T t : second) first.add(t);   // disregards duplicates
        return first;
    }
}

Map/dictionary reductions operate on dictionary types using generic <Key, Value> pairs in two stages: group elements by key, then reduce elements sharing a key into one. Reductions here can nest other reduction objects for the second stage:

public class MapUnion<K, V> implements Reduction<Map<K, V>> {
    private Reduction<V> reducer;
    MapUnion(Reduction<V> r) { reducer = r; }
    public Map<K, V> reduce(Map<K, V> first, Map<K, V> second) {
        // for each key in second, merge into first using reducer
        return first;
    }
}

1.4 Nesting reductions

Reductions on convoluted high-level structures (maps of maps) are cumbersome and error-prone to implement by hand and hurt readability. RedLib lets programmers nest reduction operations on the dictionary category, with generic types providing runtime flexibility — reducing the required code to one or two lines.

2. RedLib examples

2.1 WordCount

This application counts the frequency of patterns across documents. Each parallel task processes a document and returns a map of <Pattern, Frequency>; results are combined by summing the values for each pattern. A hand-written merge spans many lines; with RedLib it is two:

import pu.RedLib.*;
...
MapUnion<String, Integer> reducer = new MapUnion<>(new IntegerSum());
reducer.reduce(finalResult, secondMap);

An IntegerSum is nested inside a MapUnion, so the inner operation sums values sharing a key. To take the maximum instead, only the nested instance changes to IntegerMaximum.

2.2 RankedInvertedIndex

This application lists the documents containing user-specified words and the per-document frequencies — common in web search ranking. Each task returns a map of <Word, <Document, Frequency>>, and the final reduction has three layers: union of the outer maps, union of the inner <Document, Frequency> maps, and the maximum frequency where threads investigated the same document. With RedLib:

import pu.RedLib.*;
...
Reduction<Map<String, Map<String, Integer>>> reducer =
    new MapUnion<String, Map<String, Integer>>(
        new MapUnion<String, Integer>(new IntegerMaximum()));
reducer.reduce(finalResult, secondMap);

Reduction layers nest into each other, with the innermost layer performed by IntegerMaximum. Because every RedLib reduction can be declared as its base Reduction interface, polymorphism combined with nestable reductions gives programmers runtime flexibility in choosing operations.