Try this...
@Grapes( @Grab(group='org.apache.commons', module='commons-math3', version='3.6.1') ) import org.apache.commons.math3.random.MersenneTwister import groovy.time.* def benchmark = { closure -> def start = new Date() closure.call() TimeCategory.minus(new Date(), start) } def rng = new MersenneTwister() def roll = { rng.nextInt(6) + rng.nextInt(6) + rng.nextInt(6) + 3 } int N = 1000000 def results = [:].withDefault{0} def time = benchmark { (1..N).each { int n = roll() results[n]++ } } println time results.each { key, value -> println "$key: $value" } when I run it i get this output 0.565 seconds 12: 115149 14: 69665 18: 4546 6: 46159 10: 125497 11: 125188 9: 116157 8: 97236 15: 46510 7: 69281 13: 96961 5: 27663 16: 27731 3: 4572 17: 13827 4: 13858 Erick Nelson Senior Developer HD Supply, FM Cell 858-740-6523 Home 760-930-0461 CONFIDENTIALITY NOTICE: This message is for intended addressee(s) only and may contain information that is confidential, proprietary or exempt from disclosure, and subject to terms at: http://www.hdsupply.com/email. On 3/28/17, 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