[M3devel] Performance issues with Process.Wait under userthreads
Jay K
jay.krell at cornell.edu
Sat Feb 12 22:50:28 CET 2011
I don't believe there is a good solution here, other than using pthreads.
Is there? Maybe tune the wait down smaller?
I understand it stinks either way.
I don't understand the SIGCHLD stuff, at a quick glance.
Nor your changes.
However you might be the only user of user threads.
Is there any downside to your change?
(Other than more code, ok.)
Any loss of performance in any situation?
Any loss of portability?
- Jay
> To: m3devel at elegosoft.com
> Date: Sat, 12 Feb 2011 12:43:02 -0800
> From: mika at async.caltech.edu
> Subject: [M3devel] Performance issues with Process.Wait under userthreads
>
> Hi again m3devel (especially Tony),
>
> I have finally taken a bite of a problem that has been annoying me
> for a very, very long time.
>
> Under user threads, the code for Process.Wait is as follows (see
> ThreadPosix.m3):
>
> PROCEDURE WaitProcess (pid: int; VAR status: int): int =
> (* ThreadPThread.m3 and ThreadPosix.m3 are very similar. *)
> CONST Delay = 0.1D0;
> BEGIN
> LOOP
> WITH r = Uexec.waitpid(pid, ADR(status), Uexec.WNOHANG) DO
> IF r # 0 THEN RETURN r END;
> END;
> Pause(Delay);
> END;
> END WaitProcess;
>
> It inserts a 0.1 second delay after each failed waitpid. This is extremely
> annoying for programs that start a long sequence of child processes and
> wait for them in sequence. Namely, the compiler itself. As a result
> the cm3 compiler (and PM3's m3build) are normally very very slow when
> using user threads. For about the last ten years, I've had a hacked
> up m3build (for my PM3 installation) that skips the Pause and busy-waits
> instead.
>
> Note there is another problem here. Since the Modula-3 runtime ignores
> SIGCHLD, no zombie processes are created since the Unix system automatically
> reaps the child processes. I can see this would be a problem since PIDs
> are eventually reused and ... couldn't Uexec.waitpid wind up referring to
> the wrong process??
>
> I will further note that the comment in Process.i3 reads as follows:
>
> PROCEDURE Wait(p: T): ExitCode;
>
> Wait until the process with handle p terminates, then free the operating system resources associated with the process and return an exit code indicating the reason for its termination. It is a checked runtime error to call Wait twice on the same process handle.
>
> I am going to take this as fair warning that Process.Create *may* use
> resources that are not going to be released until Process.Wait has been
> called.
>
> I have modified (in my local copy of CM3) the system as follows.
> I have come up with a semi-general mechanism for immediately unblocking
> a thread on the receipt of a unix signal.
>
> 1. The system relies on changing
> ThreadPosix.XPause such that if a signal is allowed to wake up a threads,
> that fact is recorded in a new field in the thread's descriptor
> record (of type ThreadPosix.T).
>
> 2. On receipt of a waited-for unix signal, a mask is set and control
> is passed to the thread scheduler which maintains the non-zero mask for
> exactly one iteration through the thread ring.
>
> 3. If a thread is paused and waiting for EITHER a signal or some time,
> the thread is released for running and the thread's waiting state is
> cleared.
>
> The changes are more or less as follows:
>
> 1. I have added a new field of type "int" to ThreadPosix.T:
>
> (* if state = pausing, the time at which we can restart *)
> waitingForTime: Time.T;
>
> + (* if state = pausing, the signal that truncates the pause *)
> + waitingForSig: int := -1;
> +
> (* true if we are waiting during an AlertWait or AlertJoin
> or AlertPause *)
> alertable: BOOLEAN := FALSE;
>
>
> 2. Modifications to pause:
>
> + PROCEDURE SigPause(n: LONGREAL; sig: int)=
> + <*FATAL Alerted*>
> + VAR until := n + Time.Now ();
> + BEGIN
> + XPause(until, FALSE, sig);
> + END SigPause;
> +
> PROCEDURE AlertPause(n: LONGREAL) RAISES {Alerted}=
> VAR until := n + Time.Now ();
> BEGIN
> XPause(until, TRUE);
> END AlertPause;
>
> ! PROCEDURE XPause (READONLY until: Time.T; alertable := FALSE; sig:int := -1)
> ! RAISES {Alerted} =
> BEGIN
> INC (inCritical);
> self.waitingForTime := until;
> self.alertable := alertable;
> + IF sig # -1 THEN
> + self.waitingForSig := sig
> + END;
> ICannotRun (State.pausing);
> DEC (inCritical);
> InternalYield ();
>
> 3. The received-signals mask:
>
> ! CONST MaxSigs = 64;
> ! TYPE Sig = [ 0..MaxSigs-1 ];
> !
> ! (* in order to listen to other signals, they have to be enabled in
> ! allow_sigvtalrm as well *)
> ! VAR (*CONST*) SIGCHLD := ValueOfSIGCHLD();
> !
> ! gotSigs := SET OF Sig { };
> !
>
> ValueOfSIGCHLD() is a C function used to get the value of the SIGCHLD
> constant without guessing at it (in ThreadPosixC.c).
>
> 4. changes to the signal handler:
>
> ! PROCEDURE switch_thread (sig: int) RAISES {Alerted} =
> BEGIN
> allow_sigvtalrm ();
> !
> ! INC(inCritical);
> ! (* mark signal as being delivered *)
> ! IF sig >= 0 AND sig < MaxSigs THEN
> ! gotSigs := gotSigs + SET OF Sig { sig }
> ! END;
> ! DEC(inCritical);
> !
> ! IF inCritical = 0 AND heapState.inCritical = 0 THEN
> ! InternalYield ()
> ! END;
> END switch_thread;
>
> Note that I don't know if INC/DEC(inCritical) does exactly the right
> thing here.
>
> 5. changes to the scheduler:
>
> a. thread wakeup
> IF t.alertable AND t.alertPending THEN
> CanRun (t);
> EXIT;
> +
> + ELSIF t.waitingForSig IN gotSigs THEN
> + t.waitingForSig := -1;
> + CanRun(t);
> + EXIT;
>
> ELSIF t.waitingForTime <= now THEN
> CanRun (t);
> EXIT;
> !
>
> b. clearing the mask
>
> END;
> END;
>
> + gotSigs := SET OF Sig {};
> +
> IF t.state = State.alive AND (scanned OR NOT someBlocking) THEN
> IF perfOn THEN PerfRunning (t.id); END;
> (* At least one thread wants to run; transfer to it *)
>
> 6. changes to WaitProcess (Process.Wait):
>
> PROCEDURE WaitProcess (pid: int; VAR status: int): int =
> (* ThreadPThread.m3 and ThreadPosix.m3 are very similar. *)
> ! CONST Delay = 10.0D0;
> BEGIN
> LOOP
> WITH r = Uexec.waitpid(pid, ADR(status), Uexec.WNOHANG) DO
> IF r # 0 THEN RETURN r END;
> END;
> ! SigPause(Delay,SIGCHLD);
> END;
> END WaitProcess;
>
> 7. install signal handler even if program is single-threaded:
>
> BEGIN
> + (* we need to call set up the signal handler for other reasons than
> + just thread switching now *)
> + setup_sigvtalrm (switch_thread);
> END ThreadPosix.
>
> 8. modify signal handler in ThreadPosixC.c to catch SIGCHLD:
>
> sigemptyset(&ThreadSwitchSignal);
> sigaddset(&ThreadSwitchSignal, SIG_TIMESLICE);
> + sigaddset(&ThreadSwitchSignal, SIGCHLD);
>
> act.sa_handler = handler;
> act.sa_flags = SA_RESTART;
> sigemptyset(&(act.sa_mask));
> if (sigaction (SIG_TIMESLICE, &act, NULL)) abort();
> + if (sigaction (SIGCHLD, &act, NULL)) abort();
>
> I'll send the complete diff in a separate message for those who want
> to study it more closely.
>
> I propose the above changes for inclusion in the current CM3 repository.
>
> Mika
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://m3lists.elegosoft.com/pipermail/m3devel/attachments/20110212/c5937df1/attachment-0002.html>
More information about the M3devel
mailing list