Hi Paul,

The milliseconds to seconds conversion was off, so that puts the real time
at ~0.5 seconds.


> println "Took ${time/100} sec"


Using the following I get somewhere close to 0.15.  Using an int array may
be worth it for higher values of N to avoid the boxing/unboxing of the ints.

@Grapes(
    @Grab(group='org.apache.commons', module='commons-math3', version=
'3.6.1')
)
import org.apache.commons.math3.random.MersenneTwister

def benchmark = { closure ->
  start = System.currentTimeMillis()
  closure.call()
  now = System.currentTimeMillis()
  now - start
}

@groovy.transform.CompileStatic
class Twister {
    static MersenneTwister rng = new MersenneTwister()

    static int roll() {
        rng.nextInt(6) + rng.nextInt(6) + rng.nextInt(6) + 3
    }

    static Map<Integer,Integer> rolls(int num) {
        Map<Integer,Integer> results = [:]
        num.times {
            int n = roll()
            results[n] = results.containsKey(n) ? results[n] + 1 : 1
        }
        return results
    }
}

int N = 1000000
def results

def time = benchmark {
    results = Twister.rolls(N)
}

println "Took ${time/1000} sec"
for (e in results.sort()) {
    println "$e.key: $e.value"
}



On Tue, Mar 28, 2017 at 1:25 PM, Paul Moore <p.f.mo...@gmail.com> wrote:

> I'm very much a newbie with Groovy, so I apologise in advance if this
> is not the right place for questions like this. I couldn't find
> anywhere else that looked like a better option - if there is somewhere
> I should have asked, feel free to redirect me.
>
> I want to write a simulation script using Groovy - this is something
> of a hobby challenge for me, I have a friend who has done a similar
> task in C++, and I'm looking for a more user-friendly language to
> write the code, while not losing too much performance over the C
> version.
>
> The code is basically to simulate "a game". The particular game is
> defined by the user, as a function that generates scores. The program
> runs the game many times, and summarises the distribution of the
> results. Basically, a Monte Carlo simulation. My current code in
> Groovy for this, using "roll 3 dice and add up the results" as the
> target game, looks as follows:
>
> @Grapes(
>     @Grab(group='org.apache.commons', module='commons-math3',
> version='3.6.1')
> )
> import org.apache.commons.math3.random.MersenneTwister
>
> def benchmark = { closure ->
>   start = System.currentTimeMillis()
>   closure.call()
>   now = System.currentTimeMillis()
>   now - start
> }
>
> rng = new MersenneTwister()
>
> int roll() {
>     rng.nextInt(6) + rng.nextInt(6) + rng.nextInt(6) + 3
> }
>
> int N = 1000000
> def results = [:]
>
> def time = benchmark {
>     N.times {
>         int n = roll()
>         results[n] = results.containsKey(n) ? results[n] + 1 : 1
>     }
> }
>
> println "Took ${time/100} sec"
> for (e in results.sort()) {
>     println "$e.key: $e.value"
> }
>
> This does exactly what I want, but takes about 5 seconds to run the
> simulation on my PC. My friend's C++ code runs a similar simulation in
> about 0.1 second. That's a massive penalty for Groovy, and likely
> means that for more realistic simulations (which would be a lot more
> complex than 3d6!) I wouldn't even be close to competitive.
>
> The code above is completely unoptimised. I know that Groovy's dynamic
> programming features can introduce some overhead, but I also get the
> impression from the documentation that by careful use of exact types,
> and similar techniques, this can be speeded up a lot (the docs claim
> potentially better than C performance in some cases).
>
> What should I be looking at to optimise the above code? The areas I
> can think of are:
>
> 1. The RNG. I assume that the apache commons code is pretty efficient,
> though. I do want a reasonably decent RNG, and I'd heard that the JVM
> RNG is not sufficient for simulation. For now I'm assuming that this
> is sufficiently good.
> 2. The roll() function. This is the core of the inner loop, and likely
> the big bottleneck. I've declared the type, which I guess is the first
> step, but I don't know what else I can do here. I tried a
> CompileStatic annotation, but that gave me errors about referencing
> the rng variable. I'm not sure what that implies - is my code doing
> something wrong in how it references the global rng variable?
> 3. Collecting the results in a map is likely not ideal. Is there a
> better data structure I should be using? I basically want to be able
> to count how many times each result appears - results will be integers
> (in certain cases I might want non-integers but I can handle them
> exceptionally) but I don't necessarily know the range in advance (so
> I'd avoid a static array unless it gives significant performance
> benefits - when I did a quick test, I got about a second faster
> runtime, noticeable, but not enough to get me anywhere near my sub-1
> second target)
>
> I tried using the GProf profiler to see if that shed any light on what
> I should do. When I ran with a realistic number of iterations it took
> forever and then failed with an out of memory error. Dropping it to
> 10000 iterations, I got
>
>  %    cumulative   self            self     total    self    total
> self    total
> time   seconds    seconds  calls  ms/call  ms/call  min ms  min ms
> max ms  max ms  name
> 34.5        0.04     0.04  10000     0.00     0.00    0.00    0.00
> 0.91    1.13  blackpool$_run_closure2$_closure3.roll
> 18.5        0.06     0.02  39984     0.00     0.00    0.00    0.00
> 0.53    0.53  java.lang.Integer.plus
> 15.9        0.08     0.02  10000     0.00     0.01    0.00    0.00
> 0.28    1.26  blackpool$_run_closure2$_closure3.doCall
> 11.0        0.10     0.01  30000     0.00     0.00    0.00    0.00
> 0.36    0.36  org.apache.commons.math3.random.MersenneTwister.nextInt
>  5.1        0.10     0.00  10000     0.00     0.00    0.00    0.00
> 0.19    0.19  java.util.LinkedHashMap.containsKey
>  4.4        0.11     0.00  10000     0.00     0.00    0.00    0.00
> 0.02    0.02  java.util.LinkedHashMap.putAt
>  4.0        0.12     0.00      1     5.25   125.62    5.25  125.62
> 5.25  125.62  java.lang.Integer.times
>  3.9        0.12     0.00   9984     0.00     0.00    0.00    0.00
> 0.02    0.02  java.util.LinkedHashMap.getAt
>  2.2        0.12     0.00      1     2.85   128.48    2.85  128.48
> 2.85  128.48  blackpool$_run_closure2.doCall
>
> But I don't really know how to interpret that. (Also, am I somehow
> using GProf wrongly? It seems like it shouldn't run out of memory
> profiling a 5-second program run...)
>
> Can anyone offer any advice on what I should be looking at here?
>
> Thanks,
> Paul
>

Reply via email to