Noob question regarding a simple Java simulation I wrote, with some background first. I'm on a 2-core laptop with hyper-threading turned off. Originally I wrote the simulation as a single thread, then decided I wanted to double the speed by using 100% cpu instead of 50%. I did some googling on multi-threading (which I'm new to) and was able to imitate the examples I found. To keep it as simple as possible, I avoided using thread synchronization and I think I successfully did so without problems (but correct me if I'm wrong).
I wrote two multi-threaded versions. They both use close to 100% CPU as intended, and their numerical outputs are the same.
Version A uses one thread per each element of FRAC[ ] (splitting into chunks if FRAC.length > #cores).
In Version B, FRAC is not an array. Version B splits the sample into subsamples of size N/(#cores). It uses one thread for each subsample.
Here's where I think it gets weird. I used a stopwatch to compare the speeds using the same parameters (a 1-million trial sample for each FRAC). When I test a single FRAC, Version A finishes in about the same time as Version B despite using a single thread and half the CPU. Likewise, when version A has a 2-element FRAC array, it finishes in about the same time, making it twice as fast as B when both are multi-threading.
So my question is, why isn't B roughly twice as fast as a single-threaded A, and equally fast (per #trials) as a multi-threaded A? How in general do I achieve speed gains with multi-threading instead of just doubling my CPU usage for no benefit?
Feel free to criticize any/all aspects of my code while you're at it.
Version A:
Code:
import java.util.Random;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class LoanKelly{
static final int SAMPLE = 1000000;
static final int ROUNDS = 1000;
static final int INTEREST = 770;
static final int BUYINS = 4;
static final int WIN_POSSIBILITIES = 51;
static final int POSSIBILITIES = 100;
static final int INSTALLMENT = 5000;
static final double MINBET = 1.0;
static double INITBR = (double)BUYINS * INSTALLMENT;
public static void main(String[] args){
final double[] FRAC = {.01,.02};
final int CORES = Runtime.getRuntime().availableProcessors();
int threads = Math.min(CORES, FRAC.length);
int threadsRemaining = Math.max(CORES, FRAC.length);
int index = 0;
ExecutorService[] pool = new ExecutorService[(int)Math.ceil((double)FRAC.length/CORES)];
for(int p=0; p<pool.length; p++){
pool[p] = Executors.newFixedThreadPool(threads);
if(p>0)
while(true)
if(pool[p-1].isTerminated())
break;
threads = Math.min(threads, threadsRemaining);
for(int c=index; c<index+threads; c++)
pool[p].submit(new compareMedian(FRAC[c]));
pool[p].shutdown();
index += threads;
threadsRemaining -= threads;
}
}
private static class compareMedian implements Callable<Void> {
private short[] br = new short[SAMPLE];
private int busts, outcome;
private int ruins = 0;
private int total_busts = 0;
private double bet, stack, bankroll, fixedFrac, medianGrowth;
Random rand = new Random();
public compareMedian(double f){
fixedFrac = f;
}
@Override
public Void call() throws Exception{
for(int e=0; e<SAMPLE; e++){
busts = 0;
stack = (double)INSTALLMENT;
bankroll = INITBR;
for(int r=0; r<ROUNDS; r++){
bet = fixedFrac * bankroll;
if(bet>stack) bet=stack;
else if(bet<MINBET){
if(stack>=MINBET) bet=MINBET;
else bet=stack; //Allows one extra bet.
}
outcome = rand.nextInt(POSSIBILITIES);
if(outcome < WIN_POSSIBILITIES){
bankroll += bet;
stack += bet;
}else{
bankroll -= bet;
stack -= bet;
if(stack==0.0){
busts++;
if(bankroll>0.0) stack=(double)INSTALLMENT;
else break;
}
}
}
if(busts>0){
total_busts += busts;
if(busts==BUYINS) busts--;
bankroll -= (double)busts*INTEREST;
}
br[e] = (short)Math.round(bankroll*.01);
if(bankroll<=0.0) ruins++;
}
Arrays.sort(br);
medianGrowth = (br[SAMPLE/2] + br[SAMPLE/2-1])*100.0/(2*INITBR);
System.out.println("N="+SAMPLE+": After "+ROUNDS+" rounds of "+fixedFrac+" ,");
if(medianGrowth>0) System.out.println("Median growth per round = " + Math.pow(medianGrowth, 1.0/ROUNDS));
else System.out.println("Median growth per round = 0");
System.out.println((double)total_busts/SAMPLE+" busts on avg, with ruin occurring "+100.0*ruins/SAMPLE+" %");
System.out.println("");
return null;
}
}
}
Version B
Code:
import java.util.Random;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class KellyLoan{
static final int CORES = Runtime.getRuntime().availableProcessors();
static int SUBSAMPLE = (int)Math.round(1000000.0/CORES);
static int SAMPLE = SUBSAMPLE*CORES;
static short[] br = new short[SAMPLE];
static final double MINBET = 1.0;
static final int POSSIBILITIES = 100;
static final int WIN_POSSIBILITIES = 51;
static final int BUYINS = 4;
static final int INSTALLMENT = 5000;
static final int INTEREST = 770;
static final int ROUNDS = 1000;
static final double FRAC =.02;
static double INITBR = (double)BUYINS * INSTALLMENT;
static int[] subset_busts = new int[CORES];
static int[] subset_ruins = new int[CORES];
public static void main(String[] args){
int total_busts = 0;
int total_ruins = 0;
double medianGrowth;
ExecutorService pool = Executors.newFixedThreadPool(CORES);
for(int c=0; c<CORES; c++)
pool.submit(new compareMedian(c));
pool.shutdown();
while(true)
if(pool.isTerminated())
break;
for(int c=0; c<CORES; c++){
total_busts += subset_busts[c];
total_ruins += subset_ruins[c];
}
Arrays.sort(br);
medianGrowth = (br[SAMPLE/2] + br[SAMPLE/2-1])*100.0/(2*INITBR);
System.out.println("N="+SAMPLE+": After "+ROUNDS+" rounds of "+FRAC+" ,");
if(medianGrowth>0.0) System.out.println("Median growth per round = " + Math.pow(medianGrowth, 1.0/ROUNDS));
else System.out.println("Median growth per round = 0");
System.out.println((double)total_busts/SAMPLE+" busts on avg, with ruin occurring "+100.0*total_ruins/SAMPLE+" %");
}
private static class compareMedian implements Callable<Void> {
private double bet, stack, bankroll;
private int busts, outcome, mindex, maxdex, batchNum;
Random rand = new Random();
public compareMedian(int c){
batchNum = c;
subset_busts[c] = 0;
subset_ruins[c] = 0;
mindex = c*SUBSAMPLE;
maxdex = mindex+SUBSAMPLE;
}
@Override
public Void call() throws Exception{
for(int e=mindex; e<maxdex; e++){
busts = 0;
stack = (double)INSTALLMENT;
bankroll = INITBR;
for(int r=0; r<ROUNDS; r++){
bet = FRAC*bankroll;
if(bet>stack) bet=stack;
else if(bet<MINBET)
if(stack>=MINBET) bet=MINBET;
else bet=stack; //Allows one extra bet.
outcome = rand.nextInt(POSSIBILITIES);
if(outcome < WIN_POSSIBILITIES){
bankroll += bet;
stack += bet;
}else{
bankroll -= bet;
stack -= bet;
if(stack==0.0){
busts++;
if(bankroll>0.0) stack=(double)INSTALLMENT;
else break;
}
}
}
if(busts>0){
subset_busts[batchNum] += busts;
if(busts==BUYINS) busts--;
bankroll -= (double)busts*INTEREST;
}
br[e] = (short)Math.round(bankroll*.01);
if(bankroll<=0.0) subset_ruins[batchNum]++;
}
return null;
}
}
}