Go concurrency isn't parallelism: Real world lessons with Monte Carlo sims
soroushjp.com
soroushjp.com
http://golang.org/pkg/runtime/
"GOMAXPROCS sets the maximum number of CPUs that can be executing simultaneously and returns the previous setting. If n < 1, it does not change the current setting. The number of logical CPUs on the local machine can be queried with NumCPU. This call will go away when the scheduler improves."
It will be nice when this requirement is eliminated.
Said differently: concurrency is easy ("just" go func() all the things), parallelism is hard (GOMAXPROCS is not enough, you'll have to go deeper)
// Initialize to use all available CPU cores
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}[0] https://github.com/sarnowski/mitigation
[1] https://github.com/sarnowski/mitigation/blob/master/mitigati...
https://code.google.com/p/go/issues/detail?id=1435
It is fair to claim that this is "non-POSIX compliance of Linux": the underlying system call interface of an operating system is not something subject to a standard. The mapping from the POSIX-compliant C library to the underlying system calls is allowed to be quite complex, and in fact most of the high-level POSIX functions map to system calls designed for much more general use cases, or arguments with different kinds of struct packing.
You really just should not have been coding directly against the system calls of the operating system: that isn't portable; and later in the discussion, this was specifically addressed ("The syscall package is not for general use. It has no documentation. That's not going to change. When we have a working Setuid etc they will be made available as part of package os."). What sucks is that they seem to have never gotten around to implementing os.Setuid.
$ go test -race -bench=.
...
WARNING: DATA RACE
Write by goroutine 4:
sync.raceWrite()
/usr/lib/go/src/pkg/sync/race.go:41 +0x35
sync.(*WaitGroup).Wait()
/usr/lib/go/src/pkg/sync/waitgroup.go:120 +0x16d
_/home/twirrim/monte.GetPiMulti()
/home/twirrim/monte/monte.go:56 +0x23a
_/home/twirrim/monte.BenchmarkGetPiMulti()
/home/twirrim/monte/monte_test.go:17 +0x62
testing.(*B).runN()
/usr/lib/go/src/pkg/testing/benchmark.go:119 +0xc0
testing.(*B).launch()
/usr/lib/go/src/pkg/testing/benchmark.go:207 +0x1ba
...
And so on.Yet in CS we talk of things being concurrent even if they're executed as cooperative threads on a single core and parallel only applies when executing concurrently (at the same time).
Parallel lines are non-intersecting lines, i.e. lines traveling in identical directions. This is a nice allusion to the way parallelism works by running identical processes that do not interact. The fact that these processes can run simultaneously is a by-product of their parallel structure.
But yeah, the term concurrent is confusing because it can be applied to things that never actually overlap in time. But I can't think of a better term off the top of my head.
Maybe, but there's nothing about parallelism that says the parallel processes have to be (a) identical or (b) not interact, if I am not mistaken.
Two threads running in parallel (and at the same time) might do totally different things (not identical) and might also interact with each other (e.g. consumer and producer threads).
Merriam-Webster offers several definitions for "parallel", though the only definition with a temporal connotation is the definition related to computing:
"relating to or being a connection in a computer system in which the bits of a byte are transmitted over separate channels at the same time"
Meanwhile, "concurrent" references "parallel" as a synonym. Perhaps the terms are rather arbitrary after all, and we just need to get comfortable with their less-than-perfect assignments.
https://existentialtype.wordpress.com/2014/04/09/parallelism... https://existentialtype.wordpress.com/2011/03/17/parallelism...
Besides the author is only sampling the PRNG 1 million times, this is hardly enough to stumble upon any periodicity in a modern PRNG. I have absolutely no idea if the PRNG provided by Go is any good or what method it is based off of.
First, you are wasting cycles and with so little work to do before reseeding as in the code shown it probably matters quite a bit.
Second, some random number generators need some warm-up time producing lower quality random numbers at the beginning.
Third, if you are reseeding faster than your seed changes, you will repeatedly consume the same sequence of random numbers. I am not sure what the resolution of Now() is, but unless it is on the order of nanoseconds this will heavily affect the shown implementation.
If the resolution is one millisecond and it took 15 seconds to execute on a single thread, then the generated random values changed only every 74 iterations.
For Monte Carlo simulations, it's in fact very bad practice to continually reseed a computation, as it makes them unrepeatable.
edit: Another issue is that I really dont like when people present speedup in %. How should 540% speedup be interpreted? It makes more sense as a ratio, so we find sequential/parallel = 10067483333/1583584841 ~= 6.36. So the parallel version achieves a speedup factor of 6.36.
So what distrinctiont are you trying to draw?
A Jan 2013 Go lang blog post
http://blog.golang.org/concurrency-is-not-parallelism
reminding people of the Jan 2012 talk Rob Pike did on the subject
https://talks.golang.org/2012/waza.slide
Feb 2011 : Rob Pike on Parallelism and Concurrency in Programming Languages
http://www.infoq.com/interviews/pike-concurrency
I'll skip all the intermediate steps from there back to
Tony Hoare
I refer to it in the post:
"Rob Pike, one of the creators of the Go language, dedicates an entire talk, "Concurrency is not Parallelism" to this ...."
Wrote the post as a real world example to demonstrate Rob's point.
I will be developing both concurrent and parallel threads in my app, so this was very enlightening.
Thanks to the author for his clear writing style and efforts to educate.
Parallel, at least in English, means to have nothing in common with others. On most OSes it is a process affined to a dedicated CPU which is configured to access a dedicated physical I/O device only. Everything else is concurrent.
* There is a race condition where wg.Wait() might execute before the wg.Add(1) runs * The wait group was not necessary as we are already waiting for all the results from the channel
Additionally, it looks like you're re-seeding your random engine inside your sample loop, which is very slow. You only need to seed the engine outside the loop at the beginning.