* @param options Relevant options: <code>OptionMapType</code>.
*/
@SuppressWarnings("rawtypes")
protected void map(final Mapper mapper, final Option... options) {
final int size = mapper.core().size();
final CommonCore cc = this.commonCore;
// Quick pass for the probably most common events
if (size <= 0) return;
if (size == 1) {
mapper.handle(0);
return;
}
// Compute the later step size and the number of threads.
final ProfileInformation profileInfo = cc.profileInformation();
final int STEP_SIZE = Math.max(size() / 10, 1);
final AtomicInteger index = new AtomicInteger();
// Test-convert the first item and measure time. If time and size are above
// a certain threshold, parallelize, otherwise map sequentially. However, in here we
// only test the first one
long delta = 0;
final ListIterator iterator = mapper.core().iterator();
while(iterator.hasNext()) {
final int i = iterator.nextIndex();
final Object o = iterator.next();
// Skipp all null elements
if(o == null) continue;
// Set the base count to the next position we should consider (in case we break the look)
index.set(i + 1);
// Now map the given value
final long start = System.nanoTime();
mapper.handle(i);
delta = System.nanoTime() - start;
break;
}
// Next, we check if have a speed gain when we move parallel. In general, we do not
// have a speed gain when the time it takes to spawn threads takes longer than it would
// take to finish the loop single-threaded
final int toGo = size - index.get();
final long estTime = delta * toGo;
// Request a CPU for each element we have (in case we have many, we only receive maxCPU, in case we have
// very few, we don't block all CPUs.
final int NUM_THREADS = Math.min(toGo, this.commonCore.profileInformation().numCPUs); // cc.requestCPUs(toGo);
// We use a safetey factor of 2 for the fork time (FIXME: Should investigate what's the best factor),
// also, we only spawn something if there is more than one element still to go.
if((estTime < 2 * profileInfo.forkTime && toGo > 1) || NUM_THREADS < 2) {
// Instantly release all CPUs when we go singlethreaded
// this.commonCore.releaseCPUs(NUM_THREADS);
// In this case, we go single threaded
while(iterator.hasNext()) {
final int i = iterator.nextIndex();
iterator.next(); // We need to get the next() that the nextIndex increases.
mapper.handle(i);
}
return;
}
// TODO: Get proper value for step size (same problem, see below)
// TODO: Check size, if small, don't do all this setup in here ...
// NAH, even for two objects we can have a speed gain if the calls
// are very slow ...
// Okay, in this case the loop was broken and we decided to go parallel. In that case
// setup the barrier and spawn threads for all our processors so that we process the array.
final CyclicBarrier barrier = new CyclicBarrier(NUM_THREADS + 1);
final AtomicInteger baseCount = new AtomicInteger();
final Runnable runner = new Runnable() {
public void run() {
int bc = baseCount.getAndIncrement() * STEP_SIZE;
int lower = Math.max(index.get(), bc);
// Get new basecount for every pass ...
while (lower < size) {
final int max = Math.min(Math.min(lower + STEP_SIZE, size), bc + STEP_SIZE);
// Pass over all elements
for (int i = lower; i < max; i++) {
mapper.handle(i);
}
bc = baseCount.getAndIncrement() * STEP_SIZE;
lower = bc;
}
// Signal finish
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
};
// Execute all our runnables.
for(int i=0; i<NUM_THREADS; i++) {
cc.executor().getExecutor().execute(runner);
}
// Wait for all threads to finish ...
try {
barrier.await();