Sure, I realize how difficult it is to assess these things, especially sight
unseen. I was mostly fishing to see if there was a known issue with the
concurrency API or anything like that.
But let me answer your questions and I'll post the part that invokes
concurrency as well.
At the moment I don't have access to a Linux box, but I'll see if I can get
this stuff run on one.
Not synchronizing on anything. When I posted the output, I was in fact using
Math.random() API, which, according to the JDK Javadocs, can synchronize, so I
switched to using java.util.Random.nextInt(int) off a separate Random object,
which is what the Javadoc tells you to do if you're running in multiple
threads. That actually hurt the single-threaded run by a few seconds and
didn't seem to affect the concurrent mode.
I've run it in both server and client mode with not perceptible difference.
I've also experimented with different heap allocations, which seems mostly to
affect the concurrent mode by causing it to run out of heap if not given enough
memory. Otherwise, there doesn't seem to be much of a difference.
As to the number of threads in concurrent mode, I've experimented with
different setups, but for now I've settled on running each player as separate
Future objects, so that makes 3 threads in concurrent mode, and each of those
spawns up to separate Future objects for each game play, limited to 1000
threads in concurrent mode.
Here's the interesting part:
package zinger.goats;
import java.util.*;
import java.util.concurrent.*;
public class Play
{
public static class Tester implements Callable<String>
{
protected final Player player;
protected final int tries;
protected final ExecutorService executor;
public Tester(final Player player, final int tries, final
ExecutorService executor)
{
this.player = player;
this.tries = tries;
this.executor = executor;
}
public String call()
{
return Play.testPlayer(this.player, this.tries,
this.executor);
}
}
public static void main(final String... args)
{
final long startMS = System.currentTimeMillis();
final int tries = Integer.parseInt(args[0]);
final int maxThreads = args.length > 1 ?
Integer.parseInt(args[1]) : 1;
final ExecutorService testExecutor = maxThreads > 1 ?
Executors.newFixedThreadPool(Math.min(3, maxThreads)) :
Executors.newSingleThreadExecutor();
final ExecutorService gameExecutor = maxThreads > 1 ?
Executors.newFixedThreadPool(Math.min(maxThreads,
tries)) :
Executors.newSingleThreadExecutor();
try
{
final List<Future<String>> futures = new
ArrayList<Future<String>>(3);
futures.add(testExecutor.submit(new Play.Tester(new
RandomPlayer(), tries, gameExecutor)));
futures.add(testExecutor.submit(new Play.Tester(new
NeverChangePlayer(), tries, gameExecutor)));
futures.add(testExecutor.submit(new Play.Tester(new
AlwaysChangePlayer(), tries, gameExecutor)));
for(Future<String> future : futures)
{
try
{
System.out.println(future.get());
}
catch(final InterruptedException ex)
{
ex.printStackTrace();
}
catch(final ExecutionException ex)
{
ex.printStackTrace();
}
}
}
finally
{
System.out.println("Execution time: " +
((System.currentTimeMillis() - startMS) / 1000L) + "sec.");
testExecutor.shutdown();
gameExecutor.shutdown();
}
}
public static String testPlayer(final Player player, final int tries,
final ExecutorService executor)
{
final StringBuilder sb = new StringBuilder();
int carCount = 0;
int goatCount = 0;
final int[] carDistribution = new int[3];
final int[] goatDistribution = new int[3];
final List<Game> games = new ArrayList<Game>(tries);
final List<Future<?>> futures = new ArrayList<Future<?>>(tries);
for(int i = 0; i < tries; ++i)
{
final Game game = new Game(player, new Random());
games.add(game);
futures.add(executor.submit(game));
}
for(int i = 0; i < tries; ++i)
{
try
{
futures.get(i).get();
}
catch(final InterruptedException ex)
{
ex.printStackTrace();
continue;
}
catch(final ExecutionException ex)
{
ex.printStackTrace();
continue;
}
final Game game = games.get(i);
switch(game.getPrize())
{
case CAR:
++carCount;
break;
case GOAT:
++goatCount;
break;
}
for(int doorIndex = 0; doorIndex <
game.doorView.size(); ++doorIndex)
{
final Game.Door door =
game.doorView.get(doorIndex);
switch(door.open())
{
case CAR:
++carDistribution[doorIndex];
break;
case GOAT:
++goatDistribution[doorIndex];
break;
}
}
}
sb.append(player.getDescription()).append(" won
").append(carCount).append(" cars and ").append(goatCount).append(" goats.\n");
sb.append("Cars were distributed as follows:");
for(final int prizeCount : carDistribution)
sb.append('\t').append(prizeCount);
sb.append("\nGoats were distributed as follows:");
for(final int prizeCount : goatDistribution)
sb.append('\t').append(prizeCount);
sb.append('\n');
return sb.toString();
}
}
And here's how it's invoked:
<?xml version="1.0"?>
<project name="Goats" default="run">
<property name="tries" value="1000000"/>
<property name="maxThreads" value="1000"/>
<target name="init">
<mkdir dir="${basedir}/cls"/>
</target>
<target name="compile" depends="init">
<javac destdir="${basedir}/cls" srcdir="${basedir}/src"
debug="true"/>
</target>
<target name="run" depends="compile">
<echo message="Tries: ${tries}; max threads: ${maxThreads}."/>
<echo message="Single-threaded..."/>
<echo/>
<java classname="zinger.goats.Play" classpath="${basedir}/cls"
fork="true" failonerror="true">
<!--<jvmarg value="-Xmx1000m"/>-->
<jvmarg value="-server"/>
<arg value="${tries}"/>
</java>
<echo/>
<echo message="Concurrent..."/>
<java classname="zinger.goats.Play" classpath="${basedir}/cls"
fork="true" failonerror="true">
<jvmarg value="-Xmx1000m"/>
<jvmarg value="-server"/>
<arg value="${tries}"/>
<arg value="${maxThreads}"/>
</java>
</target>
</project>
And the latest output:
Tries: 1000000; max threads: 1000.
Single-threaded...
Random Player won 499013 cars and 500987 goats.
Cars were distributed as follows: 332940 333982 333078
Goats were distributed as follows: 667060 666018 666922
Never Change Player won 333605 cars and 666395 goats.
Cars were distributed as follows: 332908 333852 333240
Goats were distributed as follows: 667092 666148 666760
Always Change Player won 665827 cars and 334173 goats.
Cars were distributed as follows: 333435 332470 334095
Goats were distributed as follows: 666565 667530 665905
Execution time: 30sec.
Concurrent...
Random Player won 500155 cars and 499845 goats.
Cars were distributed as follows: 333661 334231 332108
Goats were distributed as follows: 666339 665769 667892
Never Change Player won 333164 cars and 666836 goats.
Cars were distributed as follows: 332788 333709 333503
Goats were distributed as follows: 667212 666291 666497
Always Change Player won 666344 cars and 333656 goats.
Cars were distributed as follows: 333603 333938 332459
Goats were distributed as follows: 666397 666062 667541
Execution time: 39sec.
--- On Fri, 11/14/08, Reinier Zwitserloot <[EMAIL PROTECTED]> wrote:
> From: Reinier Zwitserloot <[EMAIL PROTECTED]>
> Subject: [The Java Posse] Re: single-threaded vs concurrent performance on
> dual-core
> To: "The Java Posse" <[email protected]>
> Date: Friday, November 14, 2008, 3:03 PM
> There are a gazillion factors at work here, from bugs in
> your code to
> JVM implementation to kernel thread scheduler. Without
> exquisite
> detail, I don't think its even possible to shed some
> light as to why
> you're seeing a slight (less than doubling is
> 'slight', in meaningful
> profiling). Even with all that info, I doubt -I- could tell
> you,
> though I cannot of course speak for other posse listeners.
>
> A few quick and dirty ones:
>
> - try this on linux 2.6. It has a kick ass thread
> scheduler.
> - are you synchronizing on anything, or did you add
> Thread.sleep/wait
> anywhere? That tends to make stuff worse.
> - are you on the server VM? I don't think this matters
> anymore in
> 6u10, though, as the mixed mode VM is pretty smart, or so I
> hear.
> - are you using exactly 2 threads, slightly more than
> that, or a
> massive amount of them?
>
>
> On Nov 14, 7:04 pm, Alexey Zinger
> <[EMAIL PROTECTED]> wrote:
> > A little while ago I wrote a simulation of the Monty
> Hall puzzle. The simulator is given 3 players of different
> strategies (one makes a decision at random, the other never
> switches doors, and the third always switches) and runs each
> player through a configurable number of games, outputting
> each player's outcomes at the end. Seems fairly simple
> and everything was working as expected.
> >
> > Seeing as I have a dual core machine (XP Pro, JRE
> 1.6_10), I looked at the way the CPU was being utilized and
> to my surprise I saw a fairly even utilization of both
> cores, despite the program being single-threaded. Then I
> decided to make the simulator multi-threaded and see what
> would happen to the utilization graph and speed of the
> application. I converted the
> > code invoking the game generation and running API to
> use Java concurrency framework, which also game me good
> control over how many threads I wanted to use or if I wanted
> to run everything single-threaded still. Once I got
> everything working again, I was dismayed to see somewhat
> uneven invocation of the cores and, more importantly, the
> multi-threaded mode seems to be running consistently slower
> than the single-threaded mode. It also uses more memory,
> which appears to be directly proportional to how many
> threads I allow it to allocate. The memory demands make
> sense, but what's the deal with worse performance in
> concurrent mode? Here is a sample output:
> >
> > Tries: 1000000.
> > Single-threaded...
> > Random Player won 500177 cars and 499823 goats.
> > Cars were distributed as follows: 332820
> 333805 333375
> > Goats were distributed as follows: 667180
> 666195 666625
> >
> > Never Change Player won 333555 cars and 666445 goats.
> > Cars were distributed as follows: 333274
> 333294 333432
> > Goats were distributed as follows: 666726
> 666706 666568
> >
> > Always Change Player won 667476 cars and 332524 goats.
> > Cars were distributed as follows: 332660
> 333696 333644
> > Goats were distributed as follows: 667340
> 666304 666356
> >
> > Execution time: 28sec.
> >
> > Concurrent...
> > Random Player won 499379 cars and 500621 goats.
> > Cars were distributed as follows: 333939
> 333019 333042
> > Goats were distributed as follows: 666061
> 666981 666958
> >
> > Never Change Player won 333384 cars and 666616 goats.
> > Cars were distributed as follows: 333502
> 331961 334537
> > Goats were distributed as follows: 666498
> 668039 665463
> >
> > Always Change Player won 666691 cars and 333309 goats.
> > Cars were distributed as follows: 334080
> 332585 333335
> > Goats were distributed as follows: 665920
> 667415 666665
> >
> > Execution time: 38sec.
> >
> > I'm using a separate random number generator for
> each game play, so that at least shouldn't be the bottle
> neck according to the Javadocs. I'd be happy to post
> the source if need be, but I'm hoping I'm missing
> something simple.
> >
> > Alexey
> > 2001 Honda CBR600F4i (CCS)
> > 1992 Kawasaki
> EX500http://azinger.blogspot.comhttp://bsheet.sourceforge.nethttp://wcollage.sourceforge.net
>
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "The
Java Posse" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/javaposse?hl=en
-~----------~----~----~----~------~----~------~--~---