Go's work-stealing scheduler
rakyll.org
rakyll.org
1. https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sL...
Does the scheduler have any awareness of NUMA or NUMA-like topologies (I'm thinking here of hyperthreading) that could influence scheduling decisions?
For some workloads and hardware configurations, the gain from doing this is quite high because you can avoid schedulers jumping between cores and in the process destroying cache layers and TLBs.
OTOH, when running in a typical cloud environment, you are at the mercy of the underlying hypervisor and thus your load profile is different. In that case, affinity has little to no effect.
Go-Lang heavily trashes your cache, and quickly moves things between cores.
---
There are underlying API's on the OS level that work well. But to my understanding Go doesn't leverage them. Also NUMA aware allocations are lacking.
Any more details to share about the differences? Something that Go could readily learn from? :)
1. https://www.theverge.com/2012/4/19/2961128/google-chief-java...
/*
* This file is available under and governed by the GNU General Public
* License version 2 only, as published by the Free Software Foundation.
* However, the following notice accompanied the original version of this
* file:
*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
https://github.com/netroby/jdk9-dev/blob/master/jdk/src/java...I wonder where the original CC-zero version is located.
Another quite beautiful model is the one by Occam Pi, which has a certain simplicity ring to it:
"The F/J framework classes -
1) have many, many levels of inheritance,
2) nested classes on top of nested classes,
3) instance variables used directly by other classes (known internally as “representation-level coupling among classes”), code from the Hackers Delight without comments about what it does,
4) homegrown deques and queues instead of standard Java™ Classes, and so much more."
How so? Is there an article where the algorithm is covered?
EDIT: There seem to be some links in the comment referenced.
* Style notes
* ===========
*
* Memory ordering relies mainly on VarHandles. This can be
* awkward and ugly, but also reflects the need to control
* outcomes across the unusual cases that arise in very racy code
* with very few invariants. All fields are read into locals
* before use, and null-checked if they are references. This is
* usually done in a "C"-like style of listing declarations at the
* heads of methods or blocks, and using inline assignments on
* first encounter. Nearly all explicit checks lead to
* bypass/return, not exception throws, because they may
* legitimately arise due to cancellation/revocation during
* shutdown.
*
* There is a lot of representation-level coupling among classes
* ForkJoinPool, ForkJoinWorkerThread, and ForkJoinTask. The
* fields of WorkQueue maintain data structures managed by
* ForkJoinPool, so are directly accessed. There is little point
* trying to reduce this, since any associated future changes in
* representations will need to be accompanied by algorithmic
* changes anyway. Several methods intrinsically sprawl because
* they must accumulate sets of consistent reads of fields held in
* local variables. There are also other coding oddities
* (including several unnecessary-looking hoisted null checks)
* that help some methods perform reasonably even when interpreted
* (not compiled).
I find the "Calamity" article disingenuous. If you read more of the comments, Lea clearly explains the source of many of his data structures and gives an overview of how it all works.For the main tests, programs were run on a 30−CPU Sun Enterprise 10000 running the Solaris Production 1.2 JVM (an early version of the 1.2.2_05 release) on Solaris 7.
And slides: http://gee.cs.oswego.edu/dl/cpjslides/fj.pdf
So if every new Goroutine I spawn does an I/O operation that takes long time to finish, the scheduler will essentially spawn the same number of OS threads because every other one is blocked and no 'spinning' thread is available? If so, creating new OS threads seems like a lot overhead comparing to run a single thread in an event loop.
Does this mean network IO is only performed when there are no idling goroutines? That can't be right... what am I not understanding, here?
I think it says that network IO is only performed where there are no runnable (not idling) goroutines.
Yes, that's what I meant. Got mixed up :)
var i int
for { i++ }
the former goroutine would never actually perform the IO?(I can't fight the feeling that I'm misunderstanding this on some level...)
package main
import (
"fmt"
"time"
)
func main() {
go func() {
time.Sleep(100 * time.Millisecond)
fmt.Println("hi")
}()
var i int
for {
i++
}
}
$ GOMAXPROCS=1 go run test.go # never prints anythingI'm unsure about how that plays with inlining, though.
Is it really this way or the other way around? I would have guessed threads which are blocked in a system call are represented by an M which is not currently scheduled by the scheduler, which means that M isn't assigned to a P.
I have been able to create at least 4999 go-routines with blocking system calls though (Two OS threads were created per go-routine / syscall), so not a problem you will run into quickly.
One point for house Go!