A monte carlo simulation for the children’s game "Nachts im Stall". We’ve been trying and trying, but never managed to win. According to this tiny simulation, chances are about 1/3 (29%).
The implementation is making heavy use of functional and ES 2015 features, namely
-
Immutable data
-
Lazy evalution
-
map, reduce
-
const, arrow functions
Look mom - no for
loops! And who did say that OO is the best for simulation?
Macbook pro Late 2013, 2.3 GHz Intel Core i7, 16 GB, Node.js 8.9.1:
Winning 290825 out of 1000000 games. node main.js 10.50s user 0.03s system 99% cpu 10.529 total
Go version:
% go test -bench=. 2017/11/25 17:16:07 Won: true 2017/11/25 17:16:07 Win rate: 27% goos: darwin goarch: amd64 pkg: github.com/jhinrichsen/nachtsImStall BenchmarkManyGames-8 1000000 1560 ns/op PASS ok github.com/jhinrichsen/nachtsImStall 1.586s
Refactored into (meta) assembler:
% go test -bench=. 2017/11/25 17:36:15 Won: true 2017/11/25 17:36:15 Win rate: 29% goos: darwin goarch: amd64 pkg: github.com/jhinrichsen/nachtsImStall BenchmarkManyGames-8 1000000 1208 ns/op PASS ok github.com/jhinrichsen/nachtsImStall 1.240s
23% faster. Not that much considering all functions went away. A quick glance at perf shows:
Showing nodes accounting for 1.15s, 100% of 1.15s total Showing top 20 nodes out of 37 flat flat% sum% cum cum% 0.27s 23.48% 23.48% 0.27s 23.48% runtime.usleep /usr/local/go/src/runtime/sys_darwin_amd64.s 0.18s 15.65% 39.13% 0.18s 15.65% sync.(*Mutex).Unlock /usr/local/go/src/sync/mutex.go 0.13s 11.30% 50.43% 0.54s 46.96% math/rand.(*Rand).Int31n /usr/local/go/src/math/rand/rand.go 0.11s 9.57% 60.00% 0.11s 9.57% sync.(*Mutex).Lock /usr/local/go/src/sync/mutex.go 0.10s 8.70% 68.70% 0.68s 59.13% math/rand.Intn /usr/local/go/src/math/rand/rand.go 0.08s 6.96% 75.65% 0.76s 66.09% github.com/jhinrichsen/nachtsImStall.Play /Users/jochen/go/src/github.com/jhinrichsen/nachtsImStall/main.go 0.07s 6.09% 81.74% 0.07s 6.09% runtime.duffcopy /usr/local/go/src/runtime/duff_amd64.s 0.06s 5.22% 86.96% 0.41s 35.65% math/rand.(*Rand).Int63 /usr/local/go/src/math/rand/rand.go 0.06s 5.22% 92.17% 0.35s 30.43% math/rand.(*lockedSource).Int63 /usr/local/go/src/math/rand/rand.go 0.04s 3.48% 95.65% 0.58s 50.43% math/rand.(*Rand).Intn /usr/local/go/src/math/rand/rand.go 0.03s 2.61% 98.26% 0.03s 2.61% runtime.mach_semaphore_signal /usr/local/go/src/runtime/sys_darwin_amd64.s 0.01s 0.87% 99.13% 0.77s 66.96% github.com/jhinrichsen/nachtsImStall.BenchmarkManyGames /Users/jochen/go/src/github.com/jhinrichsen/nachtsImStall/main_test.go 0.01s 0.87% 100% 0.01s 0.87% runtime.mach_semaphore_timedwait /usr/local/go/src/runtime/sys_darwin_amd64.s 0 0% 100% 0.41s 35.65% math/rand.(*Rand).Int31 /usr/local/go/src/math/rand/rand.go
70% runtime spent in locks and random number generation (rand is concurrent access safe).
Using entropy from /dev/urandom is 4 times slower:
BenchmarkGamesStdlib-8 1000000 1267 ns/op BenchmarkGamesDevUrandom-8 300000 4908 ns/op