On Fri, Mar 29, 2013 at 3:13 PM, Dmitry Vyukov <dvy...@google.com> wrote:
I expect to be in a minority camp on this, but I don't currently
invite the idea. I have no solid/real argument against, only
preferences/opinions, e.g.:
- I prefer writing runtime.Goshed() where _really_ necessary is
explicit. I like the explicitness in this (as in almost every other
part of Go).
sleep took 1.0003939s sleep took 1.007229564s sleep took 1.00105351s sleep took 1.001116228s sleep took 3m23.461937165s
BOOM
- I have never ran into a problem caused by cooperative scheduling
(modulo me being stupid).
- I prefer that specs _do not_ talk about this.
IIUC, preemptive scheduling can change the semantics of a program.
On Unix systems preemption can be implemented by means of signals, on Windows -- Suspend/ResumeThread, Get/SetThreadContext.
preempt() routine needs to safely derive M from P (can race with releasep()), and send a signal to the thread. The signal handler is implemented along the lines of:
I expect to be in a minority camp on this, but I don't currently
invite the idea. I have no solid/real argument against, only
preferences/opinions, e.g.:
- I prefer writing runtime.Goshed() where _really_ necessary is
explicit. I like the explicitness in this (as in almost every other
part of Go).
- I have never ran into a problem caused by cooperative scheduling
(modulo me being stupid).
On Fri, Mar 29, 2013 at 6:13 PM, Dmitry Vyukov <dvy...@google.com> wrote:
> You do not know where it is necessary, because you do not know when GCI know where it is necessary. It is necessary where other goroutines
> happens.
cannot progress w/o explicit runtime.Gosched(). Such situation can
occur only rarely and in only some very specific goroutines and in
only some very specific scenarios. But those _can_ change semantics,
IIANM.
>> IIUC, preemptive scheduling can change the semantics of a program.'Basically everywhere' means 'not guaranteed everywhere'. Not every
>
>
> Currently re-scheduling can happen on every malloc(), i.e. basically
> everywhere.
goroutine allocates memory, for example.
I would only like to have it to avoid the long GC pauses, if the pauses really cannot be fixed by other means.A much easier way might be to simply allow a go-routine to be EXCLUSIVELY locked to an OS thread and let the OS handle all these upcoming issues,because it has all the above already implemented. Details below.
On Friday, March 29, 2013 6:05:26 PM UTC+1, Jan Mercl wrote:
I expect to be in a minority camp on this, but I don't currently
invite the idea. I have no solid/real argument against, only
preferences/opinions, e.g.:
Let me join the minority camp here and maybe help with solid arguments.- I prefer writing runtime.Goshed() where _really_ necessary is
explicit. I like the explicitness in this (as in almost every other
part of Go).Me too. This explicit scheduling also helps with HPC workloads, where the scheduling noise is an issue.Testing for monopolized CPU is pretty easy in test cases calling the code that would monopolized in N goroutines,where N >> NumCPUsSpreading the CPU intensive work using OpenMP-like constructs would make it easy for everyone even with the current setup.The next problem to handle over the next months then will be getting the (already preemptive!) thread scheduler cooperate with the goroutine schedulerand still get decent performance out of it* on a wide range of hardware (from phones to big servers)* on a wide range of software/system scenarios* responsive gui app* beeing one of N background daemon tasks, so you getting lots of resources is a non-issue, you should conserve resources here.* core app of the machine, so your resources are the most important* periodically started program, where you should startup and finish fast* power conserving scenarios, where waking more CPUs or keeping a CPU busy beyond the main tasks is unwanted* on a all supported platforms including their quite different async IO frame works and there thread scheduler interactionsAnd I didn't start talking about scheduling priority settings for go-routines, scheduling classes, io scheduling, resource groups, etc.pp.Do you really want to replicate all this in user space over the next years?
> On Unix systems preemption can be implemented by means of signals, onDo realtime signals have delivery guarantees? Maybe they would be of use here.
> Windows -- Suspend/ResumeThread, Get/SetThreadContext.
> preempt() routine needs to safely derive M from P (can race with
> releasep()), and send a signal to the thread. The signal handler is
> implemented along the lines of:
>
> void
> preempthandler(int signo, siginfo_t *si, ucontext_t *uc)
> {
> if(m->nopreemption) {
> // The thread will gosched()
> // when exits from the critical section.
> m->preemptionrequested = 1;
> return;
> }
> savecontext(uc);
> altercontext(uc); // sets pc to gosched()
> }
>
> The preempted goroutine needs to be put onto the tail of global run queue,
> otherwise it can be scheduled over and over again (if put onto the local
> queue) starving other goroutines on the global run queue.
> There is some chance that signals can be lost on some unix systems, in such
> case the preemption must be retried after some period of time.
> Context RestorationI'm in favor of saving the context in G. I think it will be more work
> ===================
>
> There are 2 ways to restore the original context (registers) of a preempted
> thread:
> 1. Save context in G struct and set a special preempted flag. If the flag is
> set, scheduler restores registers from G struct and GC scans the context.
> 2. Create fake stack frame during preemption and save the context in the
> stack frame. The stack frame will restore the context and return back to the
> preemption pc. The advantage is that this does not require modification of
> scheduler/GC.
to adjust all the stack handling code to handle the fake frames, than
it is to adjust the GC to scan the context.
One of the scenarios i'm
thinking about is when preemption happens when the SP is on the edge
of a stack segment. If you allocate a new segment, you have to go
through the lessstack code when returning and I'm not sure how that
function will make sure the context isn't clobbered.
As is the case now, GC can only happen when all goroutines are at controlled point in the code
, typically called a "safepoint" (usually a call site). With pre-emptive scheduling, each goroutine that is not at a safepoint must be rolled forward to a safepoint before GC can happen - at least if a StopTheWorld event is required for the GC, which I believe is the case in our world.A common way of doing this is to start all goroutines which are not at a safepoint (i.e., a call site) and have them run to the next call site where they suspend themselves. In Go, this could be achieved by temporarily lowering the stack limits for each of the affected goroutines so that the usual stack segment overflow check will call into the runtime and then the goroutine can suspend itself (and correct the stack frame size back). Additionally, loops that don't contain any calls may need an extra check at the backward branch (or perhaps at the loop body start since there may be only one entry point but multiple backward branches), so that GC is not blocked waiting for a long-running loop.A word of caution: This is probably an old hat, but in 1997, the HotSpot JVM, at that point just acquired by Sun Micro, was exactly in the same situation: the VM was based on goroutines. The conversion from that model to the fully pre-emptive system everybody is accustomed to now was non-trivial and it took a very long time (almost a year) to get the system stable again because code all over the place was making implicit assumptions about the non-preemptive scheduling nature of the VM (as we do in Go, btw). When every bug is a GC bug, things move slowly...
>> > You do not know where it is necessary, because you do not know when GCYes I do, b/c what you wrote and my "It is necessary where other
>> > happens.
>>
>> I know where it is necessary. It is necessary where other goroutines
>> cannot progress w/o explicit runtime.Gosched(). Such situation can
>> occur only rarely and in only some very specific goroutines and in
>> only some very specific scenarios. But those _can_ change semantics,
>> IIANM.
>
>
> No, you don't, because you dont' know when another goroutine has requested
> GC.
goroutines cannot progress w/o explicit runtime.Gosched()." are in no
conflict AFAICS. Let me expand: If I write some code where some
goroutine makes no progress whatsover and makes progress after
inserting Goshed in the appropriate place then I know I needed to
insert Goshed.
> On the other hand, rescheduling can happen on allocation of a temp variableQuoting http://golang.org/pkg/runtime/#Gosched
> in the code. If Gosched() changes semantics of (breaks) your code, you have
> bad/buggy program. Gosched() is semantical no-op.
> Gosched yields the processor, allowing other goroutines to run.
> It does not suspend the current goroutine, so execution resumes automatically.
This is not a semantic no-op - the documentation could be empty then
(but for GOMAXPROCS == 1 it's a guaranteed yield)
Go programs can
legitimately change behavior depending on what goroutines make
progress vs no progress and even the order of that is important.
Sure, let's talk. I will stop by your desk on Mon.But the general idea is that this should be handled as if the code uses unsafe.Pointer's in this place. In fact, it can be implemented as:
If you want safe points, you can emit one in the beginning of the function.