package water;
import org.junit.*;
import water.fvec.Vec;
import water.fvec.Chunk;
import water.util.PrettyPrint;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class MRTaskTest extends TestUtil {
@BeforeClass static public void setup() { stall_till_cloudsize(5); }
// test we reduce asap and do not produce more than tree_depth + P unreduced results
@Test public void test_reductions(){
for(int k = 0; k < 3; ++k) {
Key[] keys = new Key[2048]; // should have at most 10 + H2O.NUMCPUS active results
int depth = 11;
int log = 1;
while((1 << log) <= H2O.NUMCPUS)log++;
log--;
// maximum number of unreduced results given intended reduce strategy.
// (it is exact only if NUMCPUS is power of 2, otherwise it's an upper bound.)
// The max number of unreduced results it's depth of the tree per-thread
// (Some levels of the tree are used on launching the threads - hence it's (log2(numtasks) - log2(numcpus)*numcpus.
int max_unreduced_elems = (depth - log + 1)*H2O.NUMCPUS;
for (int i = 0; i < keys.length; ++i)
keys[i] = Key.make((byte) 1, Key.HIDDEN_USER_KEY, true, H2O.SELF);
final AtomicInteger cntr = new AtomicInteger();
final AtomicInteger maxCntr = new AtomicInteger();
new MRTask() {
public void map(Key k) {
int cnt = cntr.incrementAndGet();
int max = maxCntr.get();
while (cnt > max) {
maxCntr.compareAndSet(max, cnt);
max = maxCntr.get();
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
public void reduce(MRTask t) {
cntr.decrementAndGet();
}
}.doAll(keys);
int max_cnt = maxCntr.get();
System.out.println("max cnt = " + max_cnt);
int cnt = cntr.get();
assertEquals("Number of reductions should be (numtasks - 1). We add 1 per map, subtract one per reduce, there should be 1 left, got " + cnt,1,cnt);
assertTrue("too many unreduced results, should be <= " + max_unreduced_elems + " but was " + max_cnt, max_cnt <= max_unreduced_elems);
}
}
// Test speed of calling 1M map calls
@Test
public void testMillionMaps() {
//
final int iters = 20;
// Brutal 1-row-per-chunk, forced to 1M chunks
//final long nchunks = 1000000L;
final long nchunks = 100000L; // Cheapen for daily use
Vec zeros = Vec.makeCon(0.0,nchunks,0,true);
// Warmup: 3 untimed iterations
manyMaps(zeros);
manyMaps(zeros);
manyMaps(zeros);
long sum=0, ssq=0;
// Time a few iterations
MRTask mrt = null;
long start = System.currentTimeMillis();
for( int i=0; i<iters; i++ ) {
mrt = manyMaps(zeros);
long now = System.currentTimeMillis();
long deltams = now-start;
//System.out.println("Time in ms for "+nchunks+" maps:"+PrettyPrint.msecs(deltams,false)+", "+(deltams*1e6/nchunks)+"ns/map");
sum += deltams;
ssq += deltams*deltams;
start = now;
}
// uncomment to see the slowest-path through the MRTask tree.
//System.out.println(mrt.profString());
double avg = (double)sum/iters;
// var = mean of squares - square of means
double stddev = Math.sqrt((double)ssq/iters - avg*avg);
double pct_stddev = stddev/avg;
System.out.println("Avg map call: "+avg*1e6/nchunks+"ns/map, stddev for "+nchunks+" maps: +/-"+PrettyPrint.formatPct(pct_stddev));
zeros.remove();
}
private static MRTask manyMaps(Vec vec) {
return new MRTask() {
@Override public void map(Chunk cs[]) { }
}.profile().doAll(vec);
}
}